如何将所有Python代码打包成一个单独的zip文件?

45
将所有的 Python 应用程序打包成一个单一的 zip 文件可以方便地进行分发,只需分发一个可执行文件和一个 zip 文件。可执行文件是一个自定义二进制文件,它只需要启动,加载 zip 文件的主函数并启动 Python 或类似操作系统。虽然网上有相关讨论,但没有实际的示例说明如何完成此操作。
如果 egg 是安全的,可以将其转换为 zip 文件,但不确定如何将多个 egg 文件合并成一个 zip 文件以及如何从特定的 egg 文件中加载和运行代码,并确保其依赖项(即其他 egg 文件)在同一 zip 文件中。常见的解答是使用 py2exe,但这不是本文关心的问题。

3
对于所有问为什么要用Zip,或者为什么它不好等问题的人们......可以问问庞大的Java社区为什么会将所有东西打包在JAR/WAR中,但仍然保持强大。问题是如何实现一个单一的Python捆绑包,其中包含代码和库,并且只需要安装Python,一切都会自动工作。我有一个很长的列表,列出了什么情况下需要使用它。 - Ravi Kumar
7个回答

48
你可以使用常规的Python工具来自动化大部分工作。让我们从干净的虚拟环境开始。
[zart@feena ~]$ mkdir ziplib-demo
[zart@feena ~]$ cd ziplib-demo
[zart@feena ziplib-demo]$ virtualenv .
New python executable in ./bin/python
Installing setuptools.............done.
Installing pip...............done.

现在让我们安装一组将进入压缩库的软件包。诀窍是强制将它们安装到特定的目录中。
(注意:不要在命令行或pip.conf/pip.ini中使用--egg选项,因为这会破坏文件布局,使其无法在zip中导入)
[zart@feena ziplib-demo]$ bin/pip install --install-option --install-lib=$PWD/unpacked waitress
Downloading/unpacking waitress
  Downloading waitress-0.8.5.tar.gz (112kB): 112kB downloaded
  Running setup.py egg_info for package waitress

Requirement already satisfied (use --upgrade to upgrade): setuptools in ./lib/python2.7/site-packages/setuptools-0.6c11-py2.7.egg (from waitress)
Installing collected packages: waitress
  Running setup.py install for waitress

    Installing waitress-serve script to /home/zart/ziplib-demo/bin
Successfully installed waitress
Cleaning up...

更新:现在pip有-t <path>开关,它做的事情与--install-option --install-lib=相同。

现在让我们把它们全部打包成一个zip文件。

[zart@feena ziplib-demo]$ cd unpacked
[zart@feena unpacked]$ ls
waitress  waitress-0.8.5-py2.7.egg-info
[zart@feena unpacked]$ zip -r9 ../library.zip *
  adding: waitress/ (stored 0%)
  adding: waitress/receiver.py (deflated 71%)
  adding: waitress/server.pyc (deflated 64%)
  adding: waitress/utilities.py (deflated 62%)
  adding: waitress/trigger.pyc (deflated 63%)
  adding: waitress/trigger.py (deflated 61%)
  adding: waitress/receiver.pyc (deflated 60%)
  adding: waitress/adjustments.pyc (deflated 51%)
  adding: waitress/compat.pyc (deflated 56%)
  adding: waitress/adjustments.py (deflated 60%)
  adding: waitress/server.py (deflated 68%)
  adding: waitress/channel.py (deflated 72%)
  adding: waitress/task.pyc (deflated 57%)
  adding: waitress/tests/ (stored 0%)
  adding: waitress/tests/test_regression.py (deflated 63%)
  adding: waitress/tests/test_functional.py (deflated 88%)
  adding: waitress/tests/test_parser.pyc (deflated 76%)
  adding: waitress/tests/test_trigger.pyc (deflated 73%)
  adding: waitress/tests/test_init.py (deflated 72%)
  adding: waitress/tests/test_utilities.pyc (deflated 78%)
  adding: waitress/tests/test_buffers.pyc (deflated 79%)
  adding: waitress/tests/test_trigger.py (deflated 82%)
  adding: waitress/tests/test_buffers.py (deflated 86%)
  adding: waitress/tests/test_runner.py (deflated 75%)
  adding: waitress/tests/test_init.pyc (deflated 69%)
  adding: waitress/tests/__init__.pyc (deflated 21%)
  adding: waitress/tests/support.pyc (deflated 48%)
  adding: waitress/tests/test_utilities.py (deflated 73%)
  adding: waitress/tests/test_channel.py (deflated 87%)
  adding: waitress/tests/test_task.py (deflated 87%)
  adding: waitress/tests/test_functional.pyc (deflated 82%)
  adding: waitress/tests/__init__.py (deflated 5%)
  adding: waitress/tests/test_compat.pyc (deflated 53%)
  adding: waitress/tests/test_receiver.pyc (deflated 79%)
  adding: waitress/tests/test_adjustments.py (deflated 78%)
  adding: waitress/tests/test_adjustments.pyc (deflated 74%)
  adding: waitress/tests/test_server.pyc (deflated 73%)
  adding: waitress/tests/fixtureapps/ (stored 0%)
  adding: waitress/tests/fixtureapps/filewrapper.pyc (deflated 59%)
  adding: waitress/tests/fixtureapps/getline.py (deflated 37%)
  adding: waitress/tests/fixtureapps/nocl.py (deflated 47%)
  adding: waitress/tests/fixtureapps/sleepy.pyc (deflated 44%)
  adding: waitress/tests/fixtureapps/echo.py (deflated 40%)
  adding: waitress/tests/fixtureapps/error.py (deflated 52%)
  adding: waitress/tests/fixtureapps/nocl.pyc (deflated 48%)
  adding: waitress/tests/fixtureapps/getline.pyc (deflated 32%)
  adding: waitress/tests/fixtureapps/writecb.pyc (deflated 42%)
  adding: waitress/tests/fixtureapps/toolarge.py (deflated 37%)
  adding: waitress/tests/fixtureapps/__init__.pyc (deflated 20%)
  adding: waitress/tests/fixtureapps/writecb.py (deflated 50%)
  adding: waitress/tests/fixtureapps/badcl.pyc (deflated 44%)
  adding: waitress/tests/fixtureapps/runner.pyc (deflated 58%)
  adding: waitress/tests/fixtureapps/__init__.py (stored 0%)
  adding: waitress/tests/fixtureapps/filewrapper.py (deflated 74%)
  adding: waitress/tests/fixtureapps/runner.py (deflated 41%)
  adding: waitress/tests/fixtureapps/echo.pyc (deflated 42%)
  adding: waitress/tests/fixtureapps/groundhog1.jpg (deflated 24%)
  adding: waitress/tests/fixtureapps/error.pyc (deflated 48%)
  adding: waitress/tests/fixtureapps/sleepy.py (deflated 42%)
  adding: waitress/tests/fixtureapps/toolarge.pyc (deflated 43%)
  adding: waitress/tests/fixtureapps/badcl.py (deflated 45%)
  adding: waitress/tests/support.py (deflated 52%)
  adding: waitress/tests/test_task.pyc (deflated 78%)
  adding: waitress/tests/test_channel.pyc (deflated 78%)
  adding: waitress/tests/test_regression.pyc (deflated 68%)
  adding: waitress/tests/test_parser.py (deflated 80%)
  adding: waitress/tests/test_server.py (deflated 78%)
  adding: waitress/tests/test_receiver.py (deflated 87%)
  adding: waitress/tests/test_compat.py (deflated 51%)
  adding: waitress/tests/test_runner.pyc (deflated 72%)
  adding: waitress/__init__.pyc (deflated 50%)
  adding: waitress/channel.pyc (deflated 58%)
  adding: waitress/runner.pyc (deflated 54%)
  adding: waitress/buffers.py (deflated 74%)
  adding: waitress/__init__.py (deflated 61%)
  adding: waitress/runner.py (deflated 58%)
  adding: waitress/parser.py (deflated 69%)
  adding: waitress/compat.py (deflated 69%)
  adding: waitress/buffers.pyc (deflated 69%)
  adding: waitress/utilities.pyc (deflated 60%)
  adding: waitress/parser.pyc (deflated 53%)
  adding: waitress/task.py (deflated 72%)
  adding: waitress-0.8.5-py2.7.egg-info/ (stored 0%)
  adding: waitress-0.8.5-py2.7.egg-info/dependency_links.txt (stored 0%)
  adding: waitress-0.8.5-py2.7.egg-info/installed-files.txt (deflated 83%)
  adding: waitress-0.8.5-py2.7.egg-info/top_level.txt (stored 0%)
  adding: waitress-0.8.5-py2.7.egg-info/PKG-INFO (deflated 65%)
  adding: waitress-0.8.5-py2.7.egg-info/not-zip-safe (stored 0%)
  adding: waitress-0.8.5-py2.7.egg-info/SOURCES.txt (deflated 71%)
  adding: waitress-0.8.5-py2.7.egg-info/entry_points.txt (deflated 33%)
  adding: waitress-0.8.5-py2.7.egg-info/requires.txt (deflated 5%)
[zart@feena unpacked]$ cd ..

请注意,这些文件应该位于zip文件的顶层,不能只使用zip -r9 library.zip unpacked命令来压缩。

检查结果:

[zart@feena ziplib-demo]$ PYTHONPATH=library.zip python
Python 2.7.1 (r271:86832, Apr 12 2011, 16:15:16)
[GCC 4.6.0 20110331 (Red Hat 4.6.0-2)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import waitress
>>> waitress
<module 'waitress' from '/home/zart/ziplib-demo/library.zip/waitress/__init__.pyc'>
>>>
>>> from wsgiref.simple_server import demo_app
>>> waitress.serve(demo_app)
serving on http://0.0.0.0:8080
^C>>>

更新:自Python 3.5以来,还有zipapp模块可帮助将整个包捆绑成.pyz文件。对于更复杂的需求,pyinstallerpy2exepy2app可能更适合您的需求。


如何避免使用egg选项?它们似乎更改了选项标志。 - oshribr
注释明确表示不要使用该选项,pip 在很久以前就已经移除了它。反正它曾经被用于更兼容 setuptools 布局,我记得。 - Zart
最好将软件包捆绑成一个没有压缩的zip文件(即从“zip”命令中删除“-9”开关)。这样,Python解释器在导入期间不需要花费额外的时间来解压缩存档。 - Danilo Gómez

27

您可以使用标准库中的zipapp模块来创建可执行的 Python zip 归档文件。该模块自 Python 3.5 版本开始提供。

创建一个捆绑包的方法是添加一个名为__main__.py的顶层文件,这个文件将是 Python 运行 zip 可执行归档文件时要运行的脚本。

假设您的目录结构现在是这样的:

└── myapp
    ├── __main__.py
    ├── myprog1.py
    └── myprog2.py

如果你的代码有外部依赖项(例如在名为 requirements.txt 的文件中列出),请使用以下命令将它们安装到目录中:

pip3 install -r requirements.txt --target myapp/

注意1:这将使用外部依赖填充myapp/目录。

注意2:Debian / Ubuntu用户可能需要使用--system选项来运行pip3,因为Debian / Ubuntu版本的pip似乎默认使用--user

然后,使用以下命令创建zip可执行档案:

python3 -m zipapp myapp/

这将创建一个名为myapp.pyz的zip可执行归档文件,您可以通过运行以下命令来执行它:

这将创建一个名为myapp.pyz的zip可执行归档文件,您可以通过运行以下命令来执行它:

python3 myapp.pyz

当执行zip可执行归档文件时,运行的是__main__.py

如果除了Python脚本外,您还需要包含其他数据文件(例如文本文件,PNG图像等),供Python脚本使用,请参见:python: can executable zip files include data files?


11

如果ZIP文件中包含顶层的__main__.py[c]文件,Python将执行ZIP文件,就好像它们是单个脚本一样。然后,包引入还会检查__main__是否在ZIP文件内执行。

所以,请创建您的setup.py(这里重要的是py_modules = ['__main__'],以及指定所有包和其他模块)。

然后运行python setup.py bdist --format zip来创建ZIP文件。现在,如果您希望它可以被执行,可以执行以下操作。此时,您可以像执行任何其他Python脚本一样执行生成的ZIP文件。

对于Linux/Mac用户来说,阅读这篇文章可以提高便利性,虽然这可能不是您的情况,因为您提到了py2exe。

echo '#!/usr/bin/env python' > my_executable_zip
cat output_of_setup_py_bdist.zip >> my_executable_zip
chmod +x my_executable_zip

这只是将一个#!行添加到zip文件的开头,以便在Shell中运行时您不需要指定解释器。此时,您可以像系统上的任何其他二进制文件一样执行它,尽管实际上它是一个装满Python代码的压缩文件。我通常会创建一个Makefile来运行setup.py,然后进行这个转换。


1
不要忘记将新的zip文件设置为可执行文件:chmod 555 ./my_executable_zip 或者 chmod +x ./my_executable_zip - Chris Johnson

1
是的,一个zip文件/egg可以提供多个模块,因此您可以将它们合并为一个文件。但我非常怀疑这是否是一个好主意。您仍然需要安装该zip文件,并且它可能仍然与其他已安装版本冲突等。
因此,首先要问的问题是目标是什么。您为什么只想要一个文件?是为了安装方便还是分发方便,还是其他原因?
仅使用一个文件并不会使安装更容易,有其他更好的方法。您可以让安装程序自动下载和安装依赖项,这很容易做到。
而将它们放在一个zip文件中仍然意味着您需要扩展该zip文件并运行setup.py,这不太用户友好。
因此,仅拥有一个文件并不能解决很多问题,所以问题是您正在尝试解决哪个问题。

1
具体来说,将一个独立的Python应用程序分发到没有安装Python的计算机上;即从源代码构建自定义Python二进制文件,加载特定的引导字符串(例如“import blah; blah.main()”),并在导入zip文件到路径后运行。这使您能够将整个Python应用程序作为二进制文件+ zip文件分发。非常方便。它可以正常工作,但是当您依赖库时就不行了。 - Doug
还有自动下载吗?考虑到上周pypi.python.org宕机了几个小时,我认为这对于良好的用户体验来说是一个非常糟糕的想法。即使在它正常运行时下载东西也会导致一半的超时。绝对不想走这条路。 - Doug

0
你可以使用一个自解压缩zip文件,设置在解压内部蛋文件后启动Python解释器,这些蛋文件包含在同一个.exe文件中。

0

嗯,你可以在你的{app-home-dir/packages}中创建自己的“包/蛋”(例如通过复制蛋到那里),并在setup.py(setuptools)中配置额外的文件以将其全部打包为单个分发(什么是setup.py?)。请注意,在启动应用程序主函数之前,您需要告诉Python您的外部“包/蛋”位于哪里 - 通过将{app-home-dir/packages}添加到sys.path。这是创建独立软件包的最简单方法..但是,这会涉及到依赖项及其版本、Python模块与Ansi C代码混合等危险。


0

你能否将所有的蛋合并成一个zip文件?如果可以,怎么做呢?

是的,你可以。Python会从添加到sys.path中的zip归档文件中加载(参见PEP 273)。如果你将所有的Python库放在一个归档文件中,该归档文件将被视为一个目录。这就是一些py2exe、bbfreeze等工具用来隔离库的方法。

至于如何操作,这实际上取决于你的蛋是如何安装的:pip、easy_install等。逻辑是检查所有依赖的蛋,并收集它们的安装路径,然后将蛋压缩到一个归档文件中。

如何从特定的蛋中加载和运行代码?

你需要定义加载和运行。如果你说的是导入模块和包,你不需要做任何特殊的事情。这里有一篇有趣的博客文章,涉及到一些注意事项Packaging Python programs as runnable ZIP files

你如何确保那个 egg 文件中的代码可以访问所有依赖项(即 zip 文件中的其他 egg 文件)?

只要这些 egg 文件不是扩展(即 zip 安全),这就是内置的。另请参阅 zipimport


手动筛选鸡蛋并复制子文件夹真的是唯一的方法吗?我不能以某种方式创建一个包含所有.egg文件夹的单个zip文件吗?:( - Doug
另外,我刚刚尝试了一下,似乎不起作用;即pip install blah,并从所有.egg文件夹创建一个zip文件并尝试导入;根本不起作用。:( 你能否澄清一下你的第三点? - Doug

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