PYTHONPATH与sys.path的区别

117

我和另一位开发者对于使用 PYTHONPATH 还是 sys.path 来允许 Python 在用户目录(例如开发目录)中找到一个 Python 包持不同意见。

我们有一个具有典型目录结构的 Python 项目:

Project
    setup.py
    package
        __init__.py
        lib.py
        script.py

在 script.py 中,我们需要执行 import package.lib。当该包安装在 site-packages 中时,script.py 可以找到 package.lib

然而,在从用户目录中工作时,需要采取其他措施。我的解决方案是将PYTHONPATH设置为包括"~/Project"。另一位开发人员希望将这行代码放在 script.py 的开头:

sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

这样Python可以找到package.lib的本地副本。

我认为这是一个不好的想法,因为这行只对开发者或从本地副本运行的人有用,但我无法给出为什么这是个坏想法的好理由。

我们应该使用PYTHONPATHsys.path还是两者都可以?


4
看起来投票和答案分别倾向于使用PYTHON_PATH,尽管这可能是抽样误差或问题中无意识的偏差造成的。 - AJP
1
关于PATHsys.path(以及间接涉及到的PYTHONPATH)的区别,请参阅https://dev59.com/doLba4cB1Zd3GeqPgI0Y。 - tripleee
6个回答

45

如果修改路径的唯一原因是为了开发人员从其工作树中工作,那么您应该使用安装工具为您设置环境。virtualenv非常受欢迎,如果您使用setuptools,您可以直接运行setup.py develop在当前Python安装中半安装工作树。


19
你能对这个问题再做一些解释吗?即使你建立了一个conda/virtualenv环境,如何将顶层目录添加到Python路径中呢?请提供更多说明。 - compguy24
谢谢你的有用回答!另外,这个还是最新的吗?Setuptools 是处理这类事情的推荐方式吗? - Apples14

40

我讨厌PYTHONPATH。 我发现它很脆弱,很难在每个用户的基础上设置(特别是对于守护进程用户),并且随着项目文件夹的移动而跟踪起来很烦人。 我更喜欢在独立项目的调用脚本中设置sys.path

但是,sys.path.append不是解决问题的方法。 它很容易产生重复,而且无法解决.pth文件。 更好(也更易读)的方法是使用site.addsitedir

script.py通常不是执行此操作的最佳位置,因为它位于您要使其可用于路径的软件包内部。 库模块绝对不应该自己触及sys.path。 相反,通常会在软件包外部有一个散列脚本,您可以使用它来实例化和运行应用程序,并且在这个微不足道的包装器脚本中,您会放置像sys.path -frobbing这样的部署详细信息。


17
site.addsitedir 的问题在于它会对 sys.path 做一个 append 操作,这意味着安装的包将优先于本地开发的包(这可能会导致头发被拔掉)。因此需要使用 sys.path.insert(0...) 来解决这个问题。 - Eli Bendersky
5
@EliBendersky 建议更改为 sys.path.insert(1。https://dev59.com/Kmkw5IYBdhLWcg3wKXX_ - endolith
有没有比更改PYTHONPATH和os.sys两种方式更好的方法来使用pip安装自己编写的模块? - Travis_Dudeson

13

总的来说,我认为设置环境变量(如PYTHONPATH)可能不是一个好的做法。虽然这对于一次性的调试可能没问题,但是经常这样使用可能并不是个好主意。

使用环境变量会导致“这在我这里可以正常工作”的情况,当其他人报告代码问题时就会出现这种情况。而且人们可能也会在测试环境中采用同样的做法,导致一些开发者可以成功运行测试,但是另一些人运行测试时可能会失败。


有没有比更改 PYTHONPATH 和 os.sys 还要好的方法来使用 pip 安装自编写模块的方式呢? - Travis_Dudeson
如果自己编写的模块仅供项目使用,就不需要采取措施使其可通过pip安装。并非每个人编写的模块都需要/将会通过pip提供。 - sateesh

12

除了已经提到的许多其他原因之外,你还可以指出硬编码

sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

是脆弱的,因为它假定了script.py的位置--只有当script.py位于Project/package中时才能工作。如果用户决定将script.py移动/复制/符号链接到(几乎)任何其他地方,它将会失效。


如果用户决定移动包而不是脚本会怎样?这会损害PYTHONPATH方法吗?有没有比这两种方法更好的方法? - Travis_Dudeson

10

由于前面提到的原因,既不建议黑客攻击 PYTHONPATH,也不建议攻击sys.path。如果要将当前项目链接到 site-packages 文件夹中,则实际上有比使用 python setup.py develop 更好的方法,如此处所述:

pip install --editable path/to/project

如果您的项目根目录中没有 setup.py,则可以使用以下 setup.py 来开始:

from setuptools import setup
setup('project')

安装该软件包,软件包目录下应该有一个setup.py文件吗?setup.py里面有什么内容? - Travis_Dudeson

5
我认为,在这种情况下使用PYTHONPATH更好,主要是因为它不会引入(可疑的)不必要代码。
毕竟,如果你想一想,你的用户不需要那个sys.path东西,因为你的包将被安装到site-packages中,因为你会使用一个打包系统。
如果用户选择从“本地副本”运行,如你所说的,那么我观察到的通常做法是声明,如果在site-packages之外使用,需要手动将该包添加到PYTHONPATH中。

按照标准的项目目录结构,我发现我必须要做一些事情以使测试中的导入能够在项目根目录下的正常 pytest 调用中工作。使“标准”test目录位置起作用的最不侵入性的方法是在venvactivate脚本中更新PYTHONPATH。我认为这与IDE总是做的事情相当。似乎IDE可以做任何他们喜欢的东西而没有人称之为黑客行为。如果您在测试中使用tox,则不需要这样做,但是这样一来您的测试就有点疏远了。 - NeilG

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接