如何最干净地将第三方包目录添加到Python路径的开头?
我的背景是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 个回答
你可以看看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)描述了将第三方库放到你的路径上的最佳实践,我在下面列出了这些步骤:
- 创建一个虚拟环境(virtualenv)
- 在你的
setup.py
和/或需求文件中标明你的依赖项(见PyPA的“概念与分析”) - 使用pip将你的依赖项安装到虚拟环境中
- 使用pip和
-e/--editable
标志将你的项目本身安装到虚拟环境中。
不幸的是,Google App Engine与虚拟环境和pip不兼容。GAE选择阻止这些工具,以尝试将环境进行沙盒化。因此,必须使用一些技巧来绕过GAE的限制,以便使用额外或更新的第三方库。
如果你不喜欢这个限制,并且想使用标准的Python工具来管理第三方包的依赖关系,其他平台即服务提供商非常期待你的光临。
在这个回答中,我假设你知道怎么使用 setuptools
和 setup.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
和一些内部包,这些包都是用我们公司的命名空间来命名的。