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

探索

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

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


工程目录结构
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 <module>
    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 <module>
    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文件,目录关系如下:

新增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_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 文件,将模块路径添加到该文件中,一行一个路径。
这种方式更便于管理,且不需要每次在每个模块中新增代码,也不需要每次打开终端重新设置。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 模块和包 一 模块 1 什么是模块? 常见的场景:一个模块就是一个包含了python定义和声明的文件,文件名就是...
    go以恒阅读 2,261评论 0 4
  • 当前目录 和 脚本目录 参考资料:https://techibee.com/python/get-current-...
    ThomasYoungK阅读 10,778评论 0 11
  • If you quit from the Python interpreter and enter it agai...
    linyk3阅读 352评论 0 0
  • 一般项目中模块一般会分布在文件系统的不同路径中。Python解释器找到某个模块并将其导入命名空间会遵循一定...
    mysimplebook阅读 1,585评论 0 0
  • 一、Python简介和环境搭建以及pip的安装 4课时实验课主要内容 【Python简介】: Python 是一个...
    _小老虎_阅读 5,719评论 0 10