Python sys.path & PYTHONPATH

参考:

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 of PYTHONPATH.

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 by os.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* (see PYTHONHOME above). It is always appended to PYTHONPATH.

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 variable sys.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进行操作。

探索

看完上面的官方文档,如果还是不明白两者关系,接着我们继续探索下。

创建一个工程,目录结构如下:


Python sys.path & PYTHONPATH_第1张图片
工程目录结构
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,关系如下:

Python sys.path & PYTHONPATH_第2张图片
模块调用关系
(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.4Python环境。

'/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_AModule_B则需要通过代码的方式,或者终端export PYTHONPATH=/XX/YY/ZZ的方式将模块所在路径添加到sys.path,才能在终端中正常执行py文件,且PYTHONPATH在重启终端时会被重置为空。

蛋疼啊...

是否有更加便捷的方式呢?

答案是有的。

其他方式

  • 方式一:
    将自定义模块放置到 site-packages 文件夹中。
    这种方式会导致 site-packages 的管理混乱。

  • 方式二:
    使用pth文件,在 site-packages 文件中创建 .pth文件,将模块的路径写进去,一行一个路径。

.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文件,目录关系如下:

Python sys.path & PYTHONPATH_第3张图片
新增MainDemo.py文件

调用关系如下:


Python sys.path & PYTHONPATH_第4张图片
新的调用关系

代码如下:

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_AModule_B,所处路径刚好与MainDemo.py相同。正如我们前面所说的,执行Python 命令时,MainDemo.py所在路径/Volumes/HDD/wxx/script/SysPathDemo,会被自动添加到sys.path中,这也就是我们为什么不需要手动添加该路径到sys.path中,也不会再报这个错误ModuleNotFoundError的原因。

总结

并列模块AB,相互引用的话,需要手动添加模块所在路径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 环境,从而决定放置在系统安装的Pythonsite-packages下,还是 virtualenv创建的Python环境的site-packages下。缺点是不好管理,自定义模块一多,site-packages下会很混乱。

(4)在 site-packages 文件夹下,创建 .pth 文件,将模块路径添加到该文件中,一行一个路径。
这种方式更便于管理,且不需要每次在每个模块中新增代码,也不需要每次打开终端重新设置。

你可能感兴趣的:(Python sys.path & PYTHONPATH)