Python 打包程序

pyinstaller

官方文档地址:https://pyinstaller.org/en/stable/index.html

Pyinstaller 可以将Python源码打包成两种形式的可执行程序:

  • 一个文件夹,里面包含了启动程序和项目运行的依赖文件(.dll 或 .pyd 等文件)
  • 一个可执行程序。运行这个可执行程序时,会从程序中解压出运行的依赖环境到一个临时目录,然后在临时目录运行这个程序,因此相比于打包成文件夹的形式,这种可执行文件启动速度稍慢一些(需要先解压)

安装

pip install pyinstaller

安装成功后,会在你 python 的 scripts 文件夹下,出现一个 pyinstaller.exe 文件

使用

命令行执行:

pyinstaller myscript.py

pyinstaller 代指 pyinstaller.exe

执行上述命令后,会做这几件事情:

  • 生成一个 myscript.spec 在你的工作目录(以后通过运行:pyinstaller myscript.spec 也能生成可执行文件。)
  • 创建一个 build 文件夹(如果不存在的话),在 build 文件夹中写入一些打包时生成的中间文件
  • dist 文件夹中生成可执行文件(如果文件夹不存在,则会主动创建这个文件夹)

常用选项

  • -h, --help

    查看帮助信息

  • -v, --version

    查看 pyinstaller 版本

  • --distpath DIR

    指定生成的可执行文件的存放路径,默认:./dist

  • --workpath WORKPATH

    指定打包时,生成的临时工作文件的存放目录,默认: ./build

  • -y, --noconfirm

    重新打包时,不用用户确认,直接覆盖原先生成的可执行文件(不加这个选项,重新打包时中会让用户选择是否覆盖 y/N)

  • --upx-dir UPX_DIR

    Path to UPX utility (default: search the execution path);UPX 是一个实用工具,用来压缩二进制可执行文件。当 upx 可用时,这个参数可以用来指定搜索可执行程序的路径。

  • -a, --ascii

    Do not include unicode encoding support (default: included if available)

  • --clean

    Clean PyInstaller cache and remove temporary files before building.;构建程序之前,清理 pyinstaller 缓存和临时文件。

  • --log-level LEVEL

    Amount of detail in build-time console messages. LEVEL may be one of TRACE, DEBUG, INFO, WARN, ERROR, CRITICAL (default: INFO).

  • -D, --onedir

    Create a one-folder bundle containing an executable (default);重要的选项。用来将项目打包成单独一个文件夹的形式(pyinstaller 默认情况下会将程序打包成一个文件夹,文件夹内有许多项目的依赖文件,以及可执行程序的入口 exe 文件)

  • -F, --onefile

    Create a one-file bundled executable.;重要的选项。用来将项目打包成一个单独的可执行文件(直接生成一个 exe 文件。它的原理是运行它时,会从 exe 程序中解压出所需要的依赖环境到一个临时文件夹,然后在临时文件夹中执行程序。相比于打包成文件夹的形式,这种形式的程序执行时需要先解压,因此启动会稍慢)。

  • --specpath DIR

    Folder to store the generated spec file (default: current directory);指定生成的 spec 文件的路径

  • -n NAME, --name NAME

    Name to assign to the bundled app and spec file (default: first script’s basename);常用的参数,用来指定生成的可执行程序的名字。

  • --add-data <SRC;DEST or SRC:DEST>

    Additional non-binary files or folders to be added to the executable. The path separator is platform specific, os.pathsep (which is ; on Windows and : on most unix systems) is used. This option can be used multiple times.;用来将一些静态文件添加到打包的软件中,格式是:--add-data <src;dest> ,即将源文件复制到打包后的某个地方,windows 上用; 作为分隔符,unix 平台用 : 作为分隔符。这个选项可以被多次使用来指定多个文件。

  • --add-binary <SRC;DEST or SRC:DEST>

    Additional binary files to be added to the executable. See the --add-data option for more details. This option can be used multiple times.;添加二进制文件到打包后的程序

  • -p DIR, --paths DIR

    A path to search for imports (like using PYTHONPATH). Multiple paths are allowed, separated by ':', or use this option multiple times. Equivalent to supplying the pathex argument in the spec file.;指定导入包的搜索路径

  • --hidden-import MODULENAME, --hiddenimport MODULENAME

    Name an import not visible in the code of the script(s). This option can be used multiple times.;匿名导入模块

  • --collect-submodules MODULENAME

    Collect all submodules from the specified package or module. This option can be used multiple times.

  • --collect-data MODULENAME, --collect-datas MODULENAME

    Collect all data from the specified package or module. This option can be used multiple times.

  • --collect-binaries MODULENAME

    Collect all binaries from the specified package or module. This option can be used multiple times.

  • --collect-all MODULENAME

    Collect all submodules, data files, and binaries from the specified package or module. This option can be used multiple times.

  • --copy-metadata PACKAGENAME

    Copy metadata for the specified package. This option can be used multiple times.

  • --recursive-copy-metadata PACKAGENAME

    Copy metadata for the specified package and all its dependencies. This option can be used multiple times.

  • --additional-hooks-dir HOOKSPATH

    An additional path to search for hooks. This option can be used multiple times.

  • --runtime-hook RUNTIME_HOOKS

    Path to a custom runtime hook file. A runtime hook is code that is bundled with the executable and is executed before any other code or module to set up special features of the runtime environment. This option can be used multiple times.

  • --exclude-module EXCLUDES

    Optional module or package (the Python name, not the path name) that will be ignored (as though it was not found). This option can be used multiple times.;排除某些模块或包

  • --key KEY

    The key used to encrypt Python bytecode.;一个字符串,用来加密打包后的程序。

  • --splash IMAGE_FILE

    (EXPERIMENTAL) Add an splash screen with the image IMAGE_FILE to the application. The splash screen can display progress updates while unpacking.

windows 和 mac 独有的选项

  • -c, --console, --nowindowed

    打开一个控制台来进行输入和输出(默认开启)

  • -w, --windowed, --noconsole

    非常常用!关闭控制台(即运行时,不打开 cmd 黑窗口)

  • -i <FILE.ico or FILE.exe,ID or FILE.icns or Image or "NONE">, --icon <FILE.ico or FILE.exe,ID or FILE.icns or Image or "NONE">

    常用的选项,用来给程序指定一个 ico 或其他格式的图标

  • --disable-windowed-traceback

    Disable traceback dump of unhandled exception in windowed (noconsole) mode (Windows and macOS only), and instead display a message that this feature is disabled.

因为 pyinstaller 的选项非常多,因此你在命令行输入命令时,可以进行换行:

linux 使用 \ :

pyinstaller --noconfirm --log-level=WARN \
    --onefile --nowindow \
    --add-data="README:." \
    --add-data="image1.png:img" \
    --add-binary="libfoo.so:lib" \
    --hidden-import=secret1 \
    --hidden-import=secret2 \
    --upx-dir=/usr/local/share/ \
    myscript.spec

windows 使用 ^ :

pyinstaller --noconfirm --log-level=WARN ^
    --onefile --nowindow ^
    --add-data="README;." ^
    --add-data="image1.png;img" ^
    --add-binary="libfoo.so;lib" ^
    --hidden-import=secret1 ^
    --hidden-import=secret2 ^
    --icon=..\MLNMFLCN.ICO ^
    myscript.spec

示例

现有项目结构如下:

- pro
    - config
        - settings.ini
    - fav.ico
    - test.py

命令行运行:

pyinstaller -D -y -n new_name --add-data config/settings.ini;config -w -i fav.ico test.py

-D 打包成文件夹形式。-y 确认覆盖之前已经构建的程序。 -n 指定构建的程序名字。 --add-data 添加静态文件到构建的程序的指定路径(windows 用; linux 用 : 分割两个路径)。 -w 关闭命令行黑窗口。 -i 给程序指定图标

打包的软件结构如下:

- dist
    - new_name
        - config
            - settings.ini
        - new_name.exe
        ... # 其他依赖文件
        ...

spec 文件

spec 文件是打包时自动生成的文件,描述了打包时的配置。有了这个文件以后,我们以后打包软件,可以直接运行这个 spec 文件,譬如上一小节运行示例时生成的 new_name.spec 文件,我们可以命令行运行:pyinstaller new_name.spec 来重新打包软件。

自动生成的 new_name.spec 内容如下:

# -*- mode: python ; coding: utf-8 -*-


block_cipher = None


a = Analysis(
    ['test.py'],  # 打包的脚本
    pathex=[],    # 搜索包和模块的路径,由命令行 -p 参数指定
    binaries=[],  # 要打包进程序的二进制文件, 命令行 --add-binary 参数指定
    datas=[('config/settings.ini', 'config')],  # 要打包进程序的静态文件,命令行 --add-data 指定
    hiddenimports=[],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    pyz,
    a.scripts,
    [],
    exclude_binaries=True,
    name='new_name',  # 程序名字,由命令行 -n 参数指定
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    console=False,  # 命令行 -w 关闭控制台窗口
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
    icon='fav.ico',  # 命令行 -i 指定图标
)
coll = COLLECT(
    exe,
    a.binaries,
    a.zipfiles,
    a.datas,
    strip=False,
    upx=True,
    upx_exclude=[],
    name='new_name',  # 控制生成的程序的文件夹的名字
)

你可以编辑上面的 spec 文件,然后运行 pyinstaller new_name.spec 就可以按照你修改的参数进行程序打包了。

当将程序打包成一个单独可执行的 exe 程序时,可以通过 sys.argv[0] 来获取程序的执行路径。

当将程序打包成一个文件夹形式的程序时,可以通过 __file__ 来获取执行路径,不会有什么问题。

譬如下面的脚本,分别作为python源码执行、打包成文件夹程序执行、打包成单独的程序执行:

#!/usr/bin/env python3
import os
import sys

frozen = 'not'
if getattr(sys, 'frozen', False):
    # 在单独的可执行程序中运行
    frozen = 'ever so'
    bundle_dir = sys._MEIPASS
else:
    # python 环境中运行
    bundle_dir = os.path.dirname(os.path.abspath(__file__))
print('we are', frozen, 'frozen')
print('bundle dir is', bundle_dir)
print('sys.argv[0] is', sys.argv[0])
print('sys.executable is', sys.executable)
print('os.getcwd is', os.getcwd())
  1. 源码执行:
we are not frozen
bundle dir is C:\Users\wztshine\Desktop\test
sys.argv[0] is C:/Users/wztshine/Desktop/test/test.py
sys.executable is C:\Users\wztshine\Desktop\test\venv\Scripts\python.exe
os.getcwd is C:\Users\wztshine\Desktop\test
  1. 文件夹程序执行:
C:\Users\wztshine\Desktop\test\dist\test>test.exe
we are ever so frozen
bundle dir is C:\Users\wztshine\Desktop\test\dist\test
sys.argv[0] is test.exe
sys.executable is C:\Users\wztshine\Desktop\test\dist\test\test.exe
os.getcwd is C:\Users\wztshine\Desktop\test\dist\test
  1. 单独的程序:
C:\Users\wztshine\Desktop\test\dist>test.exe
we are ever so frozen
bundle dir is C:\Users\wztshine\AppData\Local\Temp\_MEI115762
sys.argv[0] is test.exe
sys.executable is C:\Users\wztshine\Desktop\test\dist\test.exe
os.getcwd is C:\Users\wztshine\Desktop\test\dist

cx-freeze

官方文档:https://cx-freeze.readthedocs.io/en/latest/setup_script.html

cx-freeze 也是一个打包程序,和 pyinstaller 类似,它也可以将程序打包成两种形式:

  • 文件夹形式的可执行程序。和 pyinstaller 不同的是,它生成的文件夹中有个 lib 文件夹,专门用来存放所有的运行环境依赖,如 dll、pyd 文件等。因此 cx-freeze 打包的文件夹结构更合理更简洁,不会像 pyinstaller 那样,一股脑儿的将所有依赖和执行程序放在同一个目录下。
  • 可安装程序。cx-freeze 可以将项目打包成一个可以安装的程序。pyinstaller 打包成的是一个可执行文件,能直接运行。这里打包的可安装程序需要先安装,才能运行。

安装

pip install --upgrade cx_Freeze

使用

想要使用 cx-freeze 打包软件,需要编写一个 setup.py 文件(文件名可以自定义):

import sys
from cx_Freeze import setup, Executable


# 尽管cx-freeze 会自动查找项目依赖的模块。但我们依然可以手动打包某些模块(os),或者排除某些模块(tkinter)
build_exe_options = {
    "packages": ["os"],   # 指定打包某些包
    "excludes": ["tkinter"],  # 指定排除某些包
    "zip_include_packages": ["encodings"]  # 将某些包打包时,压缩成 zip 格式
}

base = None

# if sys.platform == "win32":
#     base = "Win32GUI"  # 只有当打包 windows GUI 项目时,才设置这个参数,当设置成 win32GUI 时,会隐藏 cmd 黑窗口。

setup(
    name="test",
    version="0.1",
    description="My GUI application!",
    options={"build_exe": build_exe_options},  # build_exe 是一个选项,可以设置构建程序时要打包的内容
    executables=[Executable("test.py", base=base)],  # test.py 是项目运行主入口
)

build_exe 不仅可以写在上述的文件中,它还是一个命令行参数,譬如:python setup.py build_exe --zip-include-packages=encodings

在命令行执行上述模块:

python setup.py build

build 是一个选项。默认会创建一个 build 文件夹来存放打包的软件。可以通过 build --build-exe <folderName> 来指定别的文件夹。

会在当前路径下,创建 build/exe.[platform identifier].[python version] 的文件夹,里面存放了打包好的 exe 程序。

生成的文件夹如下:

exe.win-amd64-3.10
    - lib            # 这个文件夹里面包含了我们项目中所有依赖的模块(python内置模块和用户编写的模块,都会放到这里)
    - python3.dll
    - python310.dll
    - test.exe       # 项目主入口 test.py 打包成的程序,也是程序的主入口

将这个文件夹压缩,就可以将压缩包分发给别人了,别人只要解压缩,就可以运行其中的 test.exe 来运行程序。

build_exe 选项

build_exe 用来设置构建程序时需要用到资源、路径等信息。build_exe 选项有如下参数:

option name description
build_exe directory for built executables and dependent files; 指定打包后的软件存放的文件夹
optimize optimization level, one of 0 (disabled), 1 or 2
excludes comma-separated list of names of modules to exclude;逗号分隔的模块名组成的列表,来排除某些模块
includes comma-separated list of names of modules to include;逗号分隔的模块名组成的列表,来添加某些模块
packages comma-separated list of packages to include, which includes all submodules in the package;逗号分隔的包名组成的列表,会将包下面的所有模块添加进来
replace_paths comma-separated list of paths to replace in the code object of included modules, using the form =; search can be * which means all paths not already specified, leaving just the relative path to the module; multiple values are separated by the standard path separator
path comma-separated list of paths to search; the default value is sys.path;逗号分隔的路径们组成的列表,来查找模块; 默认值是 sys.path
no_compress create a zipfile with no compression
constants comma-separated list of constant values to include in the constants module called BUILD_CONSTANTS in the form =
bin_includes list of files to include when determining dependencies of binary files that would normally be excluded, using first the full file name, then just the base file name, then the file name without any version numbers (the version numbers that normally follow the shared object extension are stripped prior to performing the comparison);用来添加一些二进制文件。
bin_excludes list of files to exclude when determining dependencies of binary files that would normally be included, using first the full file name, then just the base file name, then the file name without any version numbers (the version numbers that normally follow the shared object extension are stripped prior to performing the comparison)
bin_path_includes list of paths from which to include files when determining dependencies of binary files;要添加的二进制文件的路径
bin_path_excludes list of paths from which to exclude files when determining dependencies of binary files
include_files list containing files to be copied to the target directory; it is expected that this list will contain strings or 2-tuples for the source and destination; the source can be a file or a directory (in which case the tree is copied except for .svn and CVS directories); the target must not be an absolute path;要打包进软件的一些静态文件,可以直接写字符串形式的文件路径,或者元组形式:(source_path, dst_path) 来将文件复制到特定的位置。
zip_includes list containing files to be included in the zip file directory; it is expected that this list will contain strings or 2-tuples for the source and destination
zip_include_packages list of packages which should be included in the zip file; the default is for all packages to be placed in the file system, not the zip file; those packages which are known to work well inside a zip file can be included if desired; use * to specify that all packages should be included in the zip file
zip_exclude_packages list of packages which should be excluded from the zip file and placed in the file system instead; the default is for all packages to be placed in the file system since a number of packages assume that is where they are found and will fail when placed in a zip file; use * to specify that all packages should be placed in the file system and excluded from the zip file (the default)
silent suppress all output except warnings (equivalent to silent_level=1)
silent_level suppress output from freeze process; can provide a value to specify what messages should be suppressed, with the possible values being:do not suppress any output [default];suppress information messages;also suppress missing-module warning messages;also suppress all other warning messages.
include_msvcr include the Microsoft Visual C runtime files without needing the redistributable package installed;打包 Microsoft Visual C++ Redistributable 的 dll 文件

命令行通过 python setup.py build_exe --help 也能查看上述所有的方法。

示例:

项目源代码结构:

- Project
    - docs  # 文档
    - proj
        - my_package  # 编写的包
        - db  # 数据库文件夹
        - splash.png  # 项目用到的静态文件
        - unzip.exe  # 要调用的外部程序
        - setup.py  # 打包配置

setup.py

import os.path
import sys

from cx_Freeze import setup, Executable

sys.path.append(os.path.dirname(os.path.dirname(__file__)))

build_exe_options = {
    'packages': ['win32api', 'xml.etree.cElementTree', 'my_package'],  # 要打包的包(my_package 是用户自己编写的 python 包)
    # 要打包的额外静态文件或文件夹,可以使用相对路径。
    'include_files': ['db', 
                      ('splash.png', 'resource'),   # 将文件复制到打包后的 resource 文件夹。如果元组第一个元素是文件夹,则只会将此文件夹中的内容(不包含当前文件夹),复制到后面定义的文件夹中。
                      'unzip.exe', 
                      '../docs'
                     ], 
    'build_exe': '../build'  # 打包后的软件存放的位置。
}

base = "Win32GUI"

setup(name="test",
      version="1",
      description="Test",
      options={"build_exe": build_exe_options,
               },
      executables=[
          Executable("./tdr.py", base=base, icon="tdr.ico"),  # icon 可以指定软件的图标
      ])

命令行运行:python setup.py build ,构建的项目结构如下:

build
    - db  # 复制过来的文件夹
    - docs  # 复制过来的文件夹
    - lib
    - python3.dll
    - python310.dll
    - resource
        - splash.png  # 复制过来的文件
    - tdr.exe
    - unzip.exe  # 复制过来的文件

bdist_msi 选项

bdist_msi 选项,可以将项目打包成可 windows 上可安装的 msi 程序。

它有如下参数:

option_name description
add_to_path add the target directory to the PATH environment variable; the default value is True if there are any console based executables and False otherwise;将安装的目标文件夹添加进环境变量
all_users perform installation for all users; the default value is False and results in an installation for just the installing user;为所有用户安装
data dictionary of arbitrary MSI data indexed by table name; for each table, a list of tuples should be provided, representing the rows that should be added to the table. For binary values (e.g. Icon.Data), pass the path to the file containing the data.
summary_data dictionary of data to include in MSI summary information stream (allowable keys are “author”, “comments”, “keywords”)
directories list of directories that should be created during installation;安装时要创建的目录
environment_variables list of environment variables that should be added to the system during installation;安装时要添加的环境变量
initial_target_dir defines the initial target directory supplied to the user during installation;安装时,给用户提供的默认安装地址
install_icon path of icon to use for the add/remove programs window that pops up during installation
product_code define the product code for the package that is created
target_name specifies the name of the file that is to be created
upgrade_code define the GUID of the upgrade code for the package that is created; this is used to force removal of any packages created with the same upgrade code prior to the installation of this one; the valid format for a GUID is {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} where X is a hex digit. Refer to Windows GUID.
extensions list of dictionaries specifying the extensions that the installed program handles. Each extension needs to specify at least the extension, a verb, and an executable. Additional allowed keys are argument to specify the invocation of the executable, mime for the extension’s mime type, and context for the context menu text.

譬如:

setup.py

import sys
from cx_Freeze import setup, Executable



build_exe_options = {
    "packages": ["os"],   # 指定打包某些包
    "excludes": ["tkinter"],  # 指定排除某些包
    "zip_include_packages": ["encodings"]  # 将某些包打包时,压缩成 zip 格式
}

bdist_msi = {
    "initial_target_dir": r"C:\Program Files\MyTest",  # 安装时默认路径
    "all_users": False  # 仅为当前用户安装程序
}

base = None

setup(
    name="test",
    version="0.1",
    description="My GUI application!",
    options={
        "build_exe": build_exe_options,  # 构建 exe 的配置
        "bdist_msi": bdist_msi  # 用户安装程序时的配置
    }, 
    executables=[Executable("test.py", base=base)],  # test.py 是项目运行主入口
)

使用命令:

python setup.py bdist_msi

会在 dist 文件夹下,创建一个 test-0.1-win64.msi 的安装包。运行这个安装包,会执行安装,安装完成后,就可以正常使用软件了。

install_exe

这个选项没啥用处,它是用来安装程序你打包的程序的。比如你使用了 bdist_msi 选项来打包一个安装程序,那么如果你打包时同时使用了 install_exe 这个选项,就可以配置 install_exe 的设置,让你在打包程序的同时,将正在打包的程序安装到某个目录下。

option name description
install_dir directory to install executables to; this defaults to a subdirectory called in the “Program Files” directory on Windows and on other platforms; on platforms other than Windows symbolic links are also created in bin for each executable.;要安装到的目录
build_dir build directory (where to install from); this defaults to the build_dir from the build command
force force installation, overwriting existing files
skip_build skip the build steps

当使用 bdist_msi 来打包成可安装程序时,可以使用 install_exe 这个选项,一边打包,一边安装正在打包的软件。

bdist_mac

为 mac 打包软件。

posted @ 2022-08-11 22:39  wztshine  阅读(657)  评论(0编辑  收藏  举报