python导入本地库文件

问题背景

在导入一个开源的gitbub项目的师傅,报错发现没有对应的目录,于是去网上找到对应的解决办法,现在做个总结

python包路径搜索机制

在解决问题前,我们先来了解一下问题的原因,可以用

  1. 来查询当前已安装的包
1
pip list package_name
  1. 在python解释器里使用sys.path
1
2
import sys
print(sys.path)

sys.path其实就是你的python解释器真正搜索包路径的顺序

python的sys.path解释

  1. sys.path 的第一个路径是脚本的执行目录,如果启动的是交互式python解释器(也就是直接在命令行启动python;或者脚本是从标准输入获取的,则这第一个路径是一个空字符串’’)

  2. 第三方库

    1
    /usr/lib/python3/site-packages:/home/user/.local/lib/python3/site-packages
  3. PYTHONPATH环境变量

    大部分老手都知道将自己的一些模块路径打入系统环境变量中,让python解释器能够找到

    1
    export PYTHONPATH=$PYTHONPATH:/path/to/my/module
  4. 各个路径下的.pth文件,每行一个路径

    这个也是最近刚搞明白的。python包的安装有一种是从本地的仓库安装

    1
    2
    3
    git clone url/to/git/repository.git
    cd repository
    pip install -e .

    这样安装的包叫做可编辑包,路径依旧是放置在原始路径下。一开始我很奇怪,这样安装python解释器里面的sys.path如何有这个路径的。后来才发现,这个路径被写入了/home/$USER/.local/lib/python3/site-packages/easy-install.pth里面 。同时,在.pth的同级目录下,会有一个xxx.egg-info文件,里面只有一行,也就是标识了包的路径,与在easy-install.pth里的路径一样。用pip uninstall xxx的时候,会把xxx.egg-info删除

python从哪里找到 .pth 文件

之前在网上看到有人说python解释器启动的时候会载入在sys.path列表下所有目录中下面以.pth结尾的文件。然而,这是个大坑!!!python根本不会这样做。百般寻找终于找到官方说明:site – 指定域的配置钩子 - Python 3.10.1 文档

site.py 包

site – 站点专属的配置钩子 - Python 3.8.12 文档

python解释器在启动的过程中会载入site包,并从中拿到要去检索的路径,将那些路径下的.pth结尾的文件,里面一行一行的路径加载入sys.path列表下,去除了不存在的路径。(.pth文件下面每一行是一个路径)

那么site包会提供哪些检索路径?

根据官方文档,site包会将sys.prefix和sys.exec_prefix作为头部,将lib/pythonX.Y/site-packages(On Unix and macOS),lib/site-packages(On Windows)作为尾部进行拼接,对于每个拼接后唯一的目录,会在该目录下搜索pth文件,并将pth文件内的路径载入sys.path. 使用 site.getsitepackages()可以获取所有的搜索路径

1
2
import site
site.getsitepackages()

然而,坑爹的地方来了!!Debian的发行版的python跟原生的python是不一样的!!!也就是说,以Ubuntu系统为例,通过Ubuntu的包管理器apt / apt-get安装的python和从源码编译的python是不完全一样的。 https://wiki.debian.org/Python 这里面说明了不一样的地方

dist-packages instead ofsite-packages. Third party Python software installed from Debian packages goes intodist-packages

What’s the difference between dist-packages and site-packages?

其实似乎还有更多不一样的地方。

实际上,查看site.py(Debian分支)的源码,可以找到site.getsitepackages()方法部分源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if os.sep == '/':
if 'VIRTUAL_ENV' in os.environ or sys.base_prefix != sys.prefix:
sitepackages.append(os.path.join(prefix, "lib",
"python" + sys.version[:3],
"site-packages"))
sitepackages.append(os.path.join(prefix, "local/lib",
"python" + sys.version[:3],
"dist-packages"))
sitepackages.append(os.path.join(prefix, "lib",
"python3",
"dist-packages"))
# this one is deprecated for Debian
sitepackages.append(os.path.join(prefix, "lib",
"python" + sys.version[:3],
"dist-packages"))

也就是说,实际上是这样的:

1
2
>>> python -c "import site; print(site.getsitepackages())"
['/usr/local/lib/python3.8/dist-packages', '/usr/lib/python3/dist-packages', '/usr/lib/python3.8/dist-packages']

而python的官方库里面,这段代码(https://github.com/python/cpython/blob/7c5b01b5101923fc38274c491bd55239ee9f0416/Lib/site.py#L319)是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def getsitepackages(prefixes=None):
"""Returns a list containing all global site-packages directories.
For each directory present in ``prefixes`` (or the global ``PREFIXES``),
this function will find its `site-packages` subdirectory depending on the
system environment, and will return a list of full paths.
"""
sitepackages = []
seen = set()

if prefixes is None:
prefixes = PREFIXES

for prefix in prefixes:
if not prefix or prefix in seen:
continue
seen.add(prefix)

if os.sep == '/':
sitepackages.append(os.path.join(prefix, "lib",
"python%d.%d" % sys.version_info[:2],
"site-packages"))
else:
sitepackages.append(prefix)
sitepackages.append(os.path.join(prefix, "lib", "site-packages"))
return sitepackages

除了以上的路径之外,还会到用户路径下搜索pth文件

对于Unix系统,会到路径:~/.local/lib/pythonX.Y/site-packages下搜索 。可以用site.getusersitepackages()方法获取这个路径

1
2
>>> python -c "import site; print(site.getusersitepackages())"
/home/$USER/.local/lib/python3.8/site-packages

坑爹的地方

  1. 被网上不靠谱的地方误导:网上很多帖子都说python会到所有的sys.path下的路径搜索pth文件,有些说加入PYTHONPATH的路径下的pth文件也会被检索到
  2. Debian的python和pip跟原生的有些区别。更具体来说用源码编译的python,会将pip install -e .的包安装到xxx/site-packages下(虽然这些路径在编译python的时候是可以设置的),而通过debian(比如Ubuntu)的包管理器(apt)安装的python,会将pip install -e .的包安装到xxx/dist-packages下

python导入包报错的解决办法

1
2
3
import sys
sys.path.append(os.path.dirname(sys,path[0]))
import mode