Python系列之指定模块加载路径

这篇文章学习了Python中包和模块的加载方式以及添加模块和包的搜索路径。在包和模块的加载方式中,学习了包括加载顺序__file__变量sys.argv[0]变量加载的包和模块的存在位置;在添加模块和包的搜索路径中,学习了暂时添加和永久添加的方法

包和模块的加载

基本概念

  • module:模块, 一个 py 文件或以其他文件形式存在的可被导入的就是一个模块
  • package:包,包含有 __init__ 文件的文件夹
  • relative path:相对路径,相对于某个目录的路径
  • absolute path:绝对路径,全路径

执行Python模块

Python 执行一个 py 文件,无论执行的方式是用绝对路径还是相对路径,interpreter 都会把文件所在的 directory 加入 sys.path 这个 list 中,而sys.path又是Python查找包和模块的列表。

1
2
3
4
5
6
7
8
#test.py
#!/usr/bin/env python
# encoding: utf-8

import os
import sys

print (sys.path[0])

绝对路径或者相对路径执行上述脚本:

1
2
3
4
5
6
7
# 使用相对路径
./test.py
/home/user/learn/python_learn/module

# 用绝对路径执行
/home/user/learn/python_learn/module/test.py
/home/user/learn/python_learn/module

相对路径和绝对路径都输出相同的结果,结果表明无论哪种执行方式,test.py 所在的文件夹都会被加入 sys.path 的首位,也就是索引为0的位置.


包和模块的加载顺序

test.py 所在的文件夹被加入到sys.path[0]是不是就意味着优先加载该目录下的module呢?这个涉及到module或者包的加载顺序问题。

Python解释器查找包,首先搜索 built-in module,其次搜索 sys.path(包括当前文件夹以及其他加载包的路径) ,这样的查找顺序将会导致同名包或模块被遮蔽

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
26
27
28
29
# 当前的文件结构
# 其中redis.py和sys.py均为空
tree .
.
├── sys.py
├── redis.py
├── test2.py
└── test.py

0 directories, 4 files

# 测试文件
cat test2.py
#!/usr/bin/env python
# encoding: utf-8

#test2.py
import sys
print (sys.path)

from redis import Redi

# 执行测试文件
./test2.py
['/home/user/learn/python_learn/module', '/home/user/anaconda3/lib/python37.zip', '/home/user/anaconda3/lib/python3.7', '/home/user/anaconda3/lib/python3.7/lib-dynload', '/home/user/.local/lib/python3.7/site-packages', '/home/user/.local/lib/python3.7/site-packages/statannot-0.1.0-py3.7.egg', '/home/user/.local/lib/python3.7/site-packages/cycler-0.10.0-py3.7.egg', '/home/softwares/anaconda3/lib/python3.7/site-packages', '/home/user/anaconda3/lib/python3.7/site-packages']
Traceback (most recent call last):
File "./test2.py", line 8, in <module>
from redis import Redis
ImportError: cannot import name 'Redis' from 'redis' (/home/user/learn/python_learn/module/redis.py)

由于 sysbuilt-in module,即使在同目录下有同名模块(sys.py文件),解释器依然可以找到正确的 sys 模块(从输出的结果可以看出),可以证实 built-in module 不会被遮蔽,拥有最高的模块加载权限;而 redis 属于第三方模块,默认安装位置是 Python 环境变量中的 site-packages,解释器启动之后会将此目录中的内容加入 sys.path,由于当前目录会在 sys.path 的首位,当前目录的 redis 优先被找到,site-packages 中的 redis 模块被遮蔽了(当前的redis.py中并没有Redi方法,所以报错)。

总的来说就是:built-in module > 当前目录 > 各种site-packages目录


模块中的file变量

__file__ is the pathname of the file from which the module was loaded, if it was loaded from a file.

如果一个模块是从文件加载的,__file__ 就是该模块的路径名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 模块的内容
cat test.py
#!/usr/bin/env python
# encoding: utf-8
import sys
print (__file__)

# 加载模块的文件
cat test2.py
#!/usr/bin/env python
# encoding: utf-8
import test

# 执行test2.py
./test2.py
/home/user/learn/python_learn/module/test.py

可以看到执行的是test2.py,返回的却是test.py的路径,因为__file__就是该模块的路径名

sys.argv[0] 变量

上面提到了__file__变量,与该变量功能类似的还有sys.argv[0] 变量,两者都是返回文件的路径,但是却存在一些差异。sys.argv[0]返回的是被执行文件的路径,并且被执行文件如果是相对路径执行则返回相对路径,绝对路径执行就返回绝对路径,而__file__返回的是被加载的模块的绝对路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 模块的内容
cat test.py
#!/usr/bin/env python
# encoding: utf-8
import sys
print (__file__)
print (sys.argv[0])

# 加载模块的文件
cat test2.py
#!/usr/bin/env python
# encoding: utf-8
import test

# 执行test2.py——相对路径
./test2.py
/home/user/learn/python_learn/module/test.py
./test2.py
# 执行test2.py——绝对路径
/home/user/learn/python_learn/module/test2.py
/home/user/learn/python_learn/module/test.py
/home/user/learn/python_learn/module/test2.py

上述实例中,虽然sys.argv[0]test.py文件中,但是真正被执行还是在test2.py文件中,所以返回的是test2.py的路径。


被加载的包和module存放位置

既然 Python 是在 built-in modulesys.path 中搜索模块的,那载入的模块存放在何处?答案就是 sys.modules。模块一经载入,Python 会把这个模块加入 sys.modules 中供下次载入使用,这样可以加速模块的引入,起到缓存的作用

sys.modules其实是一个加载了的模块和包的字典,key为包和模块的名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> import sys
# 查看数据类型
>>> type(sys.modules)
<class 'dict'>
# built-in module会被预先加载
>>> sys.modules['os']
<module 'os' from '/home/user/anaconda3/lib/python3.7/os.py'>

>>> sys.modules['pandas']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'pandas'
>>> import pandas as pd
>>> sys.modules['pandas']
<module 'pandas' from '/home/softwares/anaconda3/lib/python3.7/site-packages/pandas/__init__.py'>

借助 sys.modules__file__,可以动态获取所有已加载模块目录和路径

1
2
3
4
5
6
# built-in module的路径
>>> sys.modules['os'].__file__
'/home/user/anaconda3/lib/python3.7/os.py'
# 第三方包的路径
>>> sys.modules['pandas'].__file__
'/home/softwares/anaconda3/lib/python3.7/site-packages/pandas/__init__.py'



添加模块和包的搜索路径

在使用Python的时候经常遇到有些包安装需要一个大环境的支持,而这个大环境中的依赖包可能和当前环境中的其他包存在冲突,所以比较好的解决办法就是将这种包安装在一个虚拟环境中,然后将这个虚拟环境中Python包的地址添加到正常使用的Python包的搜索路径中。

前面已经学习了Python中包和模块加载的基础知识,这里学习如何添加模块和包的搜索路径。

暂时添加

暂时添加的搜索路径会在Python关闭之后被清除,下次重新启动Python就不会再有之前添加的搜索路径信息

按照前面学习了内容,可以直接修改sys.path列表:

1
2
import sys
sys.path.append("/home/user/Library")


永久添加

永久添加就是在重新启动Python之后之前添加的搜索路径信息仍然有效,主要包括两种方法:

  • 修改|添加环境变量PYTHONPATH,Linux直接在~/.bashrc中修改即可
  • site-packages目录中增加.pth文件

PYTHONPATH

1
export PYTHONPATH=/home/user/Library:$PYTHONPATH

增加.pth文件

查看site-packages目录:

1
2
3
>>> import site
>>> site.getsitepackages()
['/home/user/anaconda3/lib/python3.7/site-packages']

site-packages目录中添加一个路径文件,如cafffe.pth必须以.pth为后缀,写上你要加入的模块文件所在的目录名称:

1
/home/user/Library/caffe



参考链接



-----本文结束感谢您的阅读-----

本文标题:Python系列之指定模块加载路径

文章作者:showteeth

发布时间:2020年04月13日 - 10:04

最后更新:2020年05月21日 - 23:15

原始链接:http://showteeth.tech/posts/59896.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

0%