如何最干净地将第三方包目录添加到Python路径的开头?

2 投票
2 回答
1433 浏览
提问于 2025-04-20 06:47

我的背景是appengine_config.py,但这其实是一个普遍的Python问题。

假设我们克隆了一个应用的代码库,这里面有一个空的文件夹lib,然后我们通过运行命令pip install -r requirements.txt --target lib来往lib里添加一些包。

dirname ='lib'
dirpath = os.path.join(os.path.dirname(__file__), dirname)

为了导入这些包,我们可以用下面的方法把这个文件夹的路径加到Python的路径最前面(我们用索引1,因为第一个位置应该保留为'.',也就是当前目录):

sys.path.insert(1, dirpath)

不过,如果lib文件夹里的某些包是命名空间包,这样做就不行了。

为了支持命名空间包,我们可以改用:

site.addsitedir(dirpath)

但是这样会把新目录加到路径的末尾,我们不想这样,因为如果我们需要用一个更新版本的包来覆盖一个平台自带的包(比如WebOb),就会有问题。

到目前为止,我的解决方案是这段代码,但我真的想让它更简单:

sys.path, remainder = sys.path[:1], sys.path[1:]
site.addsitedir(dirpath)
sys.path.extend(remainder)

有没有更简洁或者更符合Python风格的方法来实现这个?

2 个回答

0

你可以看看Stack Overflow上关于“如何在Google App Engine中管理第三方Python库?(virtualenv?pip?)”的讨论,但针对你提到的命名空间包的问题,你遇到了一个我之前提到的长期存在的问题,这个问题是关于site.addsitedir的行为,它是把路径添加到sys.path的末尾,而不是插入到第一个元素后面。欢迎你在那个讨论中分享这个用例的链接。

我想指出你说的另一件事,这可能会让人误解:

我的上下文是appengine_config.py,但这其实是一个普遍的Python问题。

这个问题实际上是由于Google App Engine的限制,导致无法安装第三方包,因此需要寻找解决办法。与其手动调整sys.path和使用site.addsitedir,在一般的Python开发中,如果你的代码使用这些方法,那就是不对的。

Python打包机构(PyPA)描述了将第三方库放到你的路径上的最佳实践,我在下面列出了这些步骤:

  1. 创建一个虚拟环境(virtualenv)
  2. 在你的setup.py和/或需求文件中标明你的依赖项(见PyPA的“概念与分析”
  3. 使用pip将你的依赖项安装到虚拟环境中
  4. 使用pip和-e/--editable标志将你的项目本身安装到虚拟环境中。

不幸的是,Google App Engine与虚拟环境和pip不兼容。GAE选择阻止这些工具,以尝试将环境进行沙盒化。因此,必须使用一些技巧来绕过GAE的限制,以便使用额外或更新的第三方库。

如果你不喜欢这个限制,并且想使用标准的Python工具来管理第三方包的依赖关系,其他平台即服务提供商非常期待你的光临。

1

在这个回答中,我假设你知道怎么使用 setuptoolssetup.py

如果你想按照标准的 setuptools 工作流程进行开发,我建议你在你的 appengine_config.py 文件中使用以下代码:

import os
import sys

if os.environ.get('CURRENT_VERSION_ID') == 'testbed-version':
    # If we are unittesting, fake the non-existence of appengine_config.
    # The error message of the import error is handled by gae and must
    # exactly match the proper string.
    raise ImportError('No module named appengine_config')


# Imports are done relative because Google app engine prohibits
# absolute imports.
lib_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'libs')
# Add every library to sys.path.
if os.path.isdir(lib_dir):
    for lib in os.listdir(lib_dir):
        if lib.endswith('.egg'):
            lib = os.path.join(lib_dir, lib)
            # Insert to override default libraries such as webob 1.1.1.
            sys.path.insert(0, lib)

然后在 setup.cfg 文件中加入这段代码:

[develop]
install-dir = libs
always-copy = true

当你输入 python setup.py develop 时,相关的库会被下载到 libs 目录下,appengine_config 会把它们添加到你的路径中。

我们在工作中使用这个方法来包含 webob==1.3.1 和一些内部包,这些包都是用我们公司的命名空间来命名的。

撰写回答