我正在尝试将包含其依赖项的脚本打包成可执行的zip文件,基本上遵循以下步骤:
$ mkdir build
$ pip install --target build -r requirements.txt
$ cp -r mypackage build
$ echo 'from mypackage import main; main()' > build/__main__.py
$ cd build
$ zip -r myscript-with-dependencies.zip *
$ echo '#!/usr/bin/env python' | cat - myscript-with-dependencies.zip \
>../dist/myscript
一般来说,除了.pth
文件似乎没有被证实之外,这种情况有效:site.addsitedir(zip_path)
会忽略它们(可能是因为it doesn't seem to have been written to understand zip files)。
这对 me 来说尤其是一个问题,因为我的某些依赖项使用的是setuptools namespace-packages,其魔力大多发生在.pth
个文件中。
在我离开之前写下像addzipsitedir
之类的东西,我想知道是否有人遇到过这类问题,我有什么明显之处吗?一般来说,zipimport docs并没有说“它只是有效!”。 (除了命名空间包和安装到鸡蛋中的东西等之外,它主要是这样做的)。
答案 0 :(得分:1)
确实,addsitedir()
不处理zip文件;但它应该很容易复制行为。
请参阅site.addsitedir()
源代码;代码只是尝试在路径上调用os.listdir()
,然后发现.pth
个文件:
try:
names = os.listdir(sitedir)
except os.error:
return
dotpth = os.extsep + "pth"
names = [name for name in names if name.endswith(dotpth)]
for name in sorted(names):
addpackage(sitedir, name, known_paths)
其中addpackage()
只是site.addpackage()
。您可以将上面的names
列表替换为zipfile中的.pth
个文件列表。您还必须复制site.addpackage()
行为,该函数希望能够读取.pth
文件。
简化,该功能确实:
with f:
for n, line in enumerate(f):
if line.startswith("#"):
continue
if line.startswith(("import ", "import\t")):
exec line
continue
line = line.rstrip()
dir, dircase = makepath(sitedir, line)
if not dircase in known_paths and os.path.exists(dir):
sys.path.append(dir)
known_paths.add(dircase)
异常处理混合在。makepath()
为site.makepath()
,known_paths
确保找到的所有路径只添加一次。
因此,实质上,.pth
命名的所有项目都会添加到您的sys.path
,但在import
开头的任何内容都会在那里执行,然后为.pth
文件提供挂钩进入site.py
加载阶段。
基于import
的包使用setuptools
钩子来构建命名空间包;这是zc
命名空间中的一个包中的一个:
import sys,types,os; p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('zc',)); ie = os.path.exists(os.path.join(p,'__init__.py')); m = not ie and sys.modules.setdefault('zc',types.ModuleType('zc')); mp = (m or []) and m.__dict__.setdefault('__path__',[]); (p not in mp) and mp.append(p)
它使用sys.modules
函数中的sitedir
局部变量在addpackage()
中创建一个空模块对象。注意os.path.exists()
电话;对于压缩蛋,这将失败(不添加空模块对象),因此您可能需要检测命名空间包并为这些包提出自己的版本。 zc
命名空间中的任何包只能确保它关注的父命名空间有一个ModuleType()
对象,__path__
属性指向{sitedir}/{packagename}/__init__.py
。
另一种方法是将命名空间的包合并到一个目录结构中。