如何将所有 python 代码捆绑到一个 zip 文件中?

Posted

技术标签:

【中文标题】如何将所有 python 代码捆绑到一个 zip 文件中?【英文标题】:How can you bundle all your python code into a single zip file? 【发布时间】:2013-07-03 10:05:56 【问题描述】:

在分发应用程序时将所有的鸡蛋合并到一个 zip 文件中会很方便,这样您只需分发一个 zip 文件和一个可执行文件(一些自定义二进制文件,只需启动, 加载 zip 文件的主要功能并启动 python 或类似功能)。

我在网上看到过一些关于这样做的讨论,但没有关于如何实际做到这一点的示例。

我知道您可以(如果它是 zip 安全的)将鸡蛋转换为 zip 文件。

我不确定的是:

你能以某种方式将所有鸡蛋合并成一个 zip 文件吗?如果有,怎么做?

您将如何从特定的 egg 加载和运行代码?

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

人们经常问这类问题并得到诸如此类的答案;使用 py2exe。是的,我明白了,这是一种解决方案。不过,这不是我要在这里问的问题...

【问题讨论】:

致所有那些问为什么 Zip,为什么它不好等等的人……问庞大的 Java 社区,为什么他们将所有东西都打包在 JAR/WAR 中并且仍然以令人敬畏的方式处于领先地位。问题是如何用 python 实现一个包含代码和 lib 的单一捆绑包,它只需要一个安装 Python 的包,一切都会自动运行。我得到了一个庞大的列表,列出了何时需要。 【参考方案1】:

您可以使用self-extracting zip file,设置为在从包含鸡蛋的同一个 .exe 文件中解压缩鸡蛋后启动 Python 解释器。

【讨论】:

【参考方案2】:

是的,一个 zip-file/egg 可以提供多个模块,因此您可以将它们组合成一个文件。然而,我高度怀疑这是一个好主意。您仍然需要安装该 zip 文件,并且它可能仍会与其他已安装的版本等发生冲突。

所以要问的第一个问题是目标是什么。为什么你只想要一个文件?是为了便于安装,还是为了便于分发,还是为了什么?

只有一个文件不会真正使安装更容易,还有其他更好的方法。您可以让安装程序自动下载并安装依赖项,这很容易。

将它们放在一个 zip 文件中仍然意味着您需要展开该 zip 文件并运行 setup.py,这对用户不太友好。

所以只有一个文件并不能真正解决很多问题,所以问题是您要解决哪个问题。

【讨论】:

专门将独立的python应用程序分发到没有安装python的计算机; IE。在将 zip 文件导入路径后,您从加载特定引导字符串(例如“import blah; blah.main()”)的源代码构建自定义 python 二进制文件。这允许您将整个 python 应用程序分发为二进制 + zip 文件。很方便。工作正常;但当您依赖库时则不然。 还有,自动下载?鉴于上周 pypi.python.org 已经宕机了好几个小时,我会说这对于良好的用户体验来说是一个可怕的想法。即使只是在它启动时下载东西也会导致一半的时间超时。绝对对走这条路不感兴趣。【参考方案3】:

好吧,您可以在您的 app-home-dir/packages 中创建您自己的“packages/eggs”(例如通过在其中处理 egg)并在 setup.py (setuptools) 中配置额外的文件来打包它作为单一发行版 (What is setup.py?)。请注意,在启动应用程序主功能之前,您需要通过将 app-home-dir/packages 添加到 sys.path 来告知 Python 您的外部“包/鸡蛋”的确切位置。这是创建独立包的简单方法..但是这会带来关于依赖项及其版本、Python 模块与 Ansi C 代码混合等方面的危险。

【讨论】:

【参考方案4】:

你能以某种方式将所有鸡蛋合并成一个 zip 文件吗?如果有,怎么做?

是的,你可以。 Python 将从 sys.path 中添加的 zip 存档加载(请参阅PEP 273)。如果将所有 python 库放在一个档案中,档案将被视为一个目录。这就是一些 py2exe、bbfreeze 等工具可以用来隔离库的操作。

至于安装方式,这实际上取决于您的 Egg 的安装方式:pip、easy_install 等。逻辑是检查所有依赖的鸡蛋并收集它们的安装路径,然后将鸡蛋压缩到存档中。

您将如何从特定的 egg 加载和运行代码?

您需要定义加载和运行。如果您正在谈论导入模块和包,则无需执行任何操作 特别的。这是一篇关于该主题的有趣博客文章,包括一些警告Packaging Python programs as runnable ZIP files

您将如何确保该 Egg 中的代码可以访问所有依赖项(即 zip 文件中的其他 Egg)?

只要鸡蛋不是扩展程序(即 zip 安全),它就是内置的。另见zipimport

【讨论】:

手动排序鸡蛋并复制子文件夹真的是唯一的方法吗?我不能以某种方式创建一个包含所有 .egg 文件夹的单个 zip 文件吗? :( 另外,我刚试过这个,它似乎不起作用; IE。 pip install blah,并从所有 .egg 文件夹创建一个 zip 文件并尝试导入;根本不起作用。 :(你能澄清一下你的第三点吗?【参考方案5】:

您可以使用常规 Python 工具自动完成大部分工作。让我们从干净的 virtualenv 开始。

[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 module 可以帮助将整个包捆绑到 .pyz 文件中。对于更复杂的需求,pyinstaller、py2exe 或 py2app 可能更符合要求。

【讨论】:

如何避免使用egg选项?看起来他们改变了选项标志。 上面的评论特别声明不要使用该选项,并且 pip 很久以前就将其删除了,无论如何。它曾经与 setuptools 布局、IIRC 更兼容。【参考方案6】:

如果 zip 文件在顶层包含一个 __main__.py[c] 文件,那么 Python 将执行 zip 文件,就好像它们是单个脚本一样。然后,包导入还将检查 __main__ 正在从内部执行的 zip 内部。

所以创建你的 setup.py(py_modules = ['__main__'] 在这里很重要,同时指定你所有的包和其他模块)。

然后运行python setup.py bdist --format 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 的 zip 文件。我通常会创建一个 makefile 来运行 setup.py,然后进行此转换。

【讨论】:

不要忘记使新的 zip 可执行文件:chmod 555 ./my_executable_zipchmod +x ./my_executable_zip【参考方案7】:

您可以使用标准库中的 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 用户可能需要为 pip3 使用 --system 选项,因为 Debian/Ubuntu 版本的 pip 似乎默认使用 --user

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

python3 -m zipapp myapp/

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

python3 myapp.pyz

执行 zip 可执行存档时,运行的是 __main__.py

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

【讨论】:

以上是关于如何将所有 python 代码捆绑到一个 zip 文件中?的主要内容,如果未能解决你的问题,请参考以下文章

如何在不使用 python 保留目录的情况下提取文件夹中的所有 .zip 扩展名?

Python - 如何将多个文件夹从路径压缩到单个 zip 文件?

如何将具有依赖项的 python 脚本打包到 zip/tar 中?

Boto3没有将zip文件上传到S3 python

如何将多个与 Vue 捆绑在一起

如何将脚本捆绑到 dmg 文件中