参考:
sys.path
PYTHONPATH
Python sys.path详细介绍
环境
- Mac
- Python 3.7.4
着急的小伙伴可翻到最后看总结。
sys.path
A list of strings that specifies the search path for modules. Initialized from the environment variable
PYTHONPATH
, plus an installation-dependent default.
As initialized upon program startup, the first item of this list,
path[0]
, is the directory containing the script that was used to invoke the Python interpreter. If the script directory is not available (e.g. if the interpreter is invoked interactively or if the script is read from standard input),path[0]
is the empty string, which directs Python to search modules in the current directory first. Notice that the script directory is inserted before the entries inserted as a result ofPYTHONPATH
.
A program is free to modify this list for its own purposes. Only strings and bytes should be added to
sys.path
; all other data types are ignored during import.
- 指定模块搜索路径的字符串列表。从环境变量PYTHONPATH初始化,加上与安装相关的默认值。
- 在程序启动时初始化,这个列表的第一项path[0]是包含用于调用Python解释器的脚本的目录。如果脚本目录不可用(例如,解释器是交互式调用的,或者脚本是从标准输入中读取的),路径[0]是空字符串,它指示Python首先搜索当前目录中的模块。注意,脚本目录是在PYTHONPATH插入条目之前插入的。
- 程序可以根据自己的目的自由地修改这个列表。只需要向sys.path添加字符串和字节;导入期间将忽略所有其他数据类型。
PYTHONPATH
Augment the default search path for module files. The format is the same as the shell’s
PATH
: one or more directory pathnames separated byos.pathsep
(e.g. colons on Unix or semicolons on Windows). Non-existent directories are silently ignored.
In addition to normal directories, individual
PYTHONPATH
entries may refer to zipfiles containing pure Python modules (in either source or compiled form). Extension modules cannot be imported from zipfiles.
The default search path is installation dependent, but generally begins with
*prefix*/lib/python*version*
(seePYTHONHOME
above). It is always appended toPYTHONPATH
.
An additional directory will be inserted in the search path in front of
PYTHONPATH
as described above under Interface options. The search path can be manipulated from within a Python program as the variablesys.path
.
- Augment the default search path for module files. The format is the same as the shell’s PATH: one or more directory pathnames separated by os.pathsep (e.g. colons on Unix or semicolons on Windows). Non-existent directories are silently ignored.
- 除了普通目录之外,各个PYTHONPATH条目还可以引用包含纯Python模块的zipfile(以源代码或编译形式)。扩展模块不能从zipfiles导入。
- 默认搜索路径依赖于安装,但通常以前缀/lib/pythonversion开始(请参阅上面的PYTHONHOME)。它总是附加到PYTHONPATH。
- 将在PYTHONPATH前面的搜索路径中插入一个附加目录,如上面的接口选项所述。搜索路径可以在Python程序中作为变量sys.path进行操作。
探索
看完上面的官方文档,如果还是不明白两者关系,接着我们继续探索下。
创建一个工程,目录结构如下:
class Module_A_Class:
def method_A(self):
print('method_A')
if __name__ == '__main__':
pass
from Module_A.Module_A_Class import *
class Module_B_Class:
def method_B(self):
Module_A_Class().method_A()
if __name__ == '__main__':
Module_B_Class().method_B()
在 Module_B
中调用 Module_A
的函数 method_A
,关系如下:
(venv) wuxiaoxindeMac-mini:SysPathDemo wuxiaoxin$ pwd
/Volumes/HDD/wxx/script/SysPathDemo
(venv) wuxiaoxindeMac-mini:SysPathDemo wuxiaoxin$ python Module_B/Module_B_Class.py
Traceback (most recent call last):
File "Module_B/Module_B_Class.py", line 14, in
from Module_A.Module_A_Class import *
ModuleNotFoundError: No module named 'Module_A'
终端下执行 Module_B_Class.py
,报ModuleNotFoundError
这个错误,同样的代码,在Pycharm
上执行,没有问题。
于是在 Module_B_Class.py
中最上面新增代码:
import sys
print(sys.path)
from Module_A.Module_A_Class import *
class Module_B_Class:
def method_B(self):
Module_A_Class().method_A()
if __name__ == '__main__':
Module_B_Class().method_B()
终端
下执行:
(venv) wuxiaoxindeMac-mini:SysPathDemo wuxiaoxin$ python Module_B/Module_B_Class.py
['/Volumes/HDD/wxx/script/SysPathDemo/Module_B',
'/Volumes/HDD/wxx/script/SysPathDemo/Module_B',
'/Users/wuxiaoxin/.pyenv/versions/3.7.4/lib/python37.zip',
'/Users/wuxiaoxin/.pyenv/versions/3.7.4/lib/python3.7',
'/Users/wuxiaoxin/.pyenv/versions/3.7.4/lib/python3.7/lib-dynload',
'/Volumes/HDD/wxx/script/SysPathDemo/venv/lib/python3.7/site-packages',
'/Volumes/HDD/wxx/script/SysPathDemo/venv/lib/python3.7/site-packages/setuptools-39.1.0-py3.7.egg',
'/Volumes/HDD/wxx/script/SysPathDemo/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg']
Traceback (most recent call last):
File "Module_B/Module_B_Class.py", line 14, in
from Module_A.Module_A_Class import *
ModuleNotFoundError: No module named 'Module_A'
Pycharm
下执行:
/Volumes/HDD/wxx/script/SysPathDemo/venv/bin/python /Volumes/HDD/wxx/script/SysPathDemo/Module_B/Module_B_Class.py
['/Volumes/HDD/wxx/script/SysPathDemo/Module_B',
'/Volumes/HDD/wxx/script/SysPathDemo',
'/Users/wuxiaoxin/.pyenv/versions/3.7.4/lib/python37.zip',
'/Users/wuxiaoxin/.pyenv/versions/3.7.4/lib/python3.7',
'/Users/wuxiaoxin/.pyenv/versions/3.7.4/lib/python3.7/lib-dynload',
'/Volumes/HDD/wxx/script/SysPathDemo/venv/lib/python3.7/site-packages',
'/Volumes/HDD/wxx/script/SysPathDemo/venv/lib/python3.7/site-packages/setuptools-39.1.0-py3.7.egg',
'/Volumes/HDD/wxx/script/SysPathDemo/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg',
'/Applications/PyCharm.app/Contents/helpers/pycharm_matplotlib_backend']
method_A
Process finished with exit code 0
可以看出,Pycharm
下运行,相比于终端
,sys.path
中多了两条路径:
'/Volumes/HDD/wxx/script/SysPathDemo'
'/Applications/PyCharm.app/Contents/helpers/pycharm_matplotlib_backend'
回顾上面终端执行 Module_B_Class.py
报的错:
ModuleNotFoundError: No module named 'Module_A'
而 Module_A
模块正好处于 /Volumes/HDD/wxx/script/SysPathDemo
路径之下。
可以看出,Pycharm
自动添加模块路径到 sys.path
中了。而这个自动步骤,我猜测是通过Module_B_Class.py
中的这一句from Module_A.Module_A_Class import *
自动识别并添加的。而终端
运行则不会自动添加。
那么我们通过代码
或者 终端
添加试试:
-
代码
方式添加:
import sys
sys.path.append('/Volumes/HDD/wxx/script/SysPathDemo')
print(sys.path)
from Module_A.Module_A_Class import *
class Module_B_Class:
def method_B(self):
Module_A_Class().method_A()
if __name__ == '__main__':
Module_B_Class().method_B()
终端中执行:
(venv) wuxiaoxindeMac-mini:SysPathDemo wuxiaoxin$ python Module_B/Module_B_Class.py
['/Volumes/HDD/wxx/script/SysPathDemo/Module_B',
'/Volumes/HDD/wxx/script/SysPathDemo/Module_B',
'/Users/wuxiaoxin/.pyenv/versions/3.7.4/lib/python37.zip',
'/Users/wuxiaoxin/.pyenv/versions/3.7.4/lib/python3.7',
'/Users/wuxiaoxin/.pyenv/versions/3.7.4/lib/python3.7/lib-dynload',
'/Volumes/HDD/wxx/script/SysPathDemo/venv/lib/python3.7/site-packages',
'/Volumes/HDD/wxx/script/SysPathDemo/venv/lib/python3.7/site-packages/setuptools-39.1.0-py3.7.egg',
'/Volumes/HDD/wxx/script/SysPathDemo/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg',
'/Volumes/HDD/wxx/script/SysPathDemo']
method_A
这下正常执行了。
-
终端
通过PYTHONPATH
添加:
先移除上面Module_B_Class.py
中添加sys.path
的这句语句:
sys.path.append('/Volumes/HDD/wxx/script/SysPathDemo')
如下:
import sys
print(sys.path)
from Module_A.Module_A_Class import *
class Module_B_Class:
def method_B(self):
Module_A_Class().method_A()
if __name__ == '__main__':
Module_B_Class().method_B()
终端中执行:
(venv) wuxiaoxindeMac-mini:SysPathDemo wuxiaoxin$ echo $PYTHONPATH
(venv) wuxiaoxindeMac-mini:SysPathDemo wuxiaoxin$ export PYTHONPATH=/Volumes/HDD/wxx/script/SysPathDemo
(venv) wuxiaoxindeMac-mini:SysPathDemo wuxiaoxin$ echo $PYTHONPATH
/Volumes/HDD/wxx/script/SysPathDemo
(venv) wuxiaoxindeMac-mini:SysPathDemo wuxiaoxin$ python Module_B/Module_B_Class.py
['/Volumes/HDD/wxx/script/SysPathDemo/Module_B',
'/Volumes/HDD/wxx/script/SysPathDemo',
'/Users/wuxiaoxin/.pyenv/versions/3.7.4/lib/python37.zip',
'/Users/wuxiaoxin/.pyenv/versions/3.7.4/lib/python3.7',
'/Users/wuxiaoxin/.pyenv/versions/3.7.4/lib/python3.7/lib-dynload',
'/Volumes/HDD/wxx/script/SysPathDemo/venv/lib/python3.7/site-packages',
'/Volumes/HDD/wxx/script/SysPathDemo/venv/lib/python3.7/site-packages/setuptools-39.1.0-py3.7.egg',
'/Volumes/HDD/wxx/script/SysPathDemo/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg',
'/Volumes/HDD/wxx/script/SysPathDemo']
method_A
从上面的操作,可以看出,通过添加到 PYTHONPATH
的路径,会自动添加到 sys.path
中。
还可以知道:
执行的
.py
文件所在的路径,会自动添加到sys.path
中,如上面的Module_B_Class .py
所在路径/Volumes/HDD/wxx/script/SysPathDemo/Module_B
,而且是添加到sys.path
中的第一个。sys.path
包含当前所依赖的Python
环境。如上面SysPathDemo
项目所依赖的Python
环境,是使用virtualenv
创建的独立Python
环境/Volumes/HDD/wxx/script/SysPathDemo/venv
不清楚 virtualenv
的可以参考我的另一篇。
'/Volumes/HDD/wxx/script/SysPathDemo/venv/lib/python3.7/site-packages',
'/Volumes/HDD/wxx/script/SysPathDemo/venv/lib/python3.7/site-packages/setuptools-39.1.0-py3.7.egg',
'/Volumes/HDD/wxx/script/SysPathDemo/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg'
-
sys.path
包含独立Python
环境的父环境,即系统安装版本号为3.7.4
的Python
环境。
'/Users/wuxiaoxin/.pyenv/versions/3.7.4/lib/python37.zip',
'/Users/wuxiaoxin/.pyenv/versions/3.7.4/lib/python3.7',
'/Users/wuxiaoxin/.pyenv/versions/3.7.4/lib/python3.7/lib-dynload'
而通过pip
安装的第三方库,是包含在~/lib/python3.7/site-packages/..
路径下了,而本地自定义的库,也就是我们上面的Module_A
和Module_B
则需要通过代码的方式,或者终端export PYTHONPATH=/XX/YY/ZZ
的方式将模块所在路径添加到sys.path
,才能在终端中正常执行py
文件,且PYTHONPATH
在重启终端时会被重置为空。
蛋疼啊...
是否有更加便捷的方式呢?
答案是有的。
其他方式
方式一:
将自定义模块放置到site-packages
文件夹中。
这种方式会导致site-packages
的管理混乱。方式二:
使用pth
文件,在site-packages
文件中创建.pth
文件,将模块的路径写进去,一行一个路径。
.pth
内容,#
开头为注释语句
# SysPathDemo project
/Volumes/HDD/wxx/script/SysPathDemo
Module_B_Class.py
内容如下
import sys
# sys.path.append('/Volumes/HDD/wxx/script/SysPathDemo')
print(sys.path)
from Module_A.Module_A_Class import *
class Module_B_Class:
def method_B(self):
Module_A_Class().method_A()
if __name__ == '__main__':
Module_B_Class().method_B()
终端执行:
(venv) wuxiaoxindeMac-mini:SysPathDemo wuxiaoxin$ python Module_B/Module_B_Class.py
['/Volumes/HDD/wxx/script/SysPathDemo/Module_B',
'/Users/wuxiaoxin/.pyenv/versions/3.7.4/lib/python37.zip',
'/Users/wuxiaoxin/.pyenv/versions/3.7.4/lib/python3.7',
'/Users/wuxiaoxin/.pyenv/versions/3.7.4/lib/python3.7/lib-dynload',
'/Volumes/HDD/wxx/script/SysPathDemo/venv/lib/python3.7/site-packages',
'/Volumes/HDD/wxx/script/SysPathDemo',
'/Volumes/HDD/wxx/script/SysPathDemo/venv/lib/python3.7/site-packages/setuptools-39.1.0-py3.7.egg',
'/Volumes/HDD/wxx/script/SysPathDemo/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg']
method_A
显然,方式二更为靠谱。
补充
以上的例子,是针对Module_B
独立运行时,引用了Module_A
的代码。当我们执行python ../Module_B/Module_B_Class.py
时,路径../Module_B
会被添加到sys.path
中。
明白这个关系之后,我们在项目中新建一个MainDemo.py
文件,目录关系如下:
调用关系如下:
代码如下:
Module_A_Class .py
class Module_A_Class:
def method_A(self):
print('method_A')
if __name__ == '__main__':
pass
Module_B_Class.py
class Module_B_Class:
def method_B(self):
print('method_B')
if __name__ == '__main__':
pass
MainDemoClass.py
import sys
print(sys.path)
from Module_A.Module_A_Class import *
from Module_B.Module_B_Class import *
class MainDemoClass:
def main_method(self):
Module_A_Class().method_A()
Module_B_Class().method_B()
if __name__ == '__main__':
MainDemoClass().main_method()
我们将前面的 .pth
内容进行注释
# SysPathDemo project
# /Volumes/HDD/wxx/script/SysPathDemo
执行python MainDemo.py
,如下:
(venv) wuxiaoxindeMac-mini:SysPathDemo wuxiaoxin$ pwd
/Volumes/HDD/wxx/script/SysPathDemo
(venv) wuxiaoxindeMac-mini:SysPathDemo wuxiaoxin$ python MainDemo.py
['/Volumes/HDD/wxx/script/SysPathDemo',
'/Users/wuxiaoxin/.pyenv/versions/3.7.4/lib/python37.zip',
'/Users/wuxiaoxin/.pyenv/versions/3.7.4/lib/python3.7',
'/Users/wuxiaoxin/.pyenv/versions/3.7.4/lib/python3.7/lib-dynload',
'/Volumes/HDD/wxx/script/SysPathDemo/venv/lib/python3.7/site-packages',
'/Volumes/HDD/wxx/script/SysPathDemo',
'/Volumes/HDD/wxx/script/SysPathDemo/venv/lib/python3.7/site-packages/setuptools-39.1.0-py3.7.egg',
'/Volumes/HDD/wxx/script/SysPathDemo/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg']
method_A
method_B
MainDemo.py
中所应用的两个模块Module_A
和Module_B
,所处路径刚好与MainDemo.py
相同。正如我们前面所说的,执行Python
命令时,MainDemo.py
所在路径/Volumes/HDD/wxx/script/SysPathDemo
,会被自动添加到sys.path
中,这也就是我们为什么不需要手动添加该路径到sys.path
中,也不会再报这个错误ModuleNotFoundError
的原因。
总结
并列模块A
和B
,相互引用的话,需要手动添加模块所在路径
到sys.path
中。
这里的模块所在路径
,假如模块路径为/Volumes/HDD/wxx/script/SysPathDemo/Module_A
,那么Module_A
模块所在路径为/Volumes/HDD/wxx/script/SysPathDemo
。
方式多种:
(1)代码方式
import sys
sys.path.append('模块所在路径')
(2)终端设置PYTHONPATH
,会被自动添加到sys.path
中。缺点是每次重启终端PYTHONPATH
都会被重置为空。
export PYTHONPATH=模块所在路径
(3)将模块放置到 site-packages
文件夹下,注意你项目依赖的Python
环境,从而决定放置在系统安装的Python
的site-packages
下,还是 virtualenv
创建的Python
环境的site-packages
下。缺点是不好管理,自定义模块一多,site-packages
下会很混乱。
(4)在 site-packages
文件夹下,创建 .pth
文件,将模块路径添加到该文件中,一行一个路径。
这种方式更便于管理,且不需要每次在每个模块中新增代码,也不需要每次打开终端重新设置。