`Python`的`打包`与`分发`:`setuptools`、`poetry`和`PyInstaller`的`用法`。

Python 打包与分发:setuptools、poetry 和 PyInstaller 的用法

大家好,今天我们来深入探讨 Python 的打包与分发,主要聚焦于三个重要的工具:setuptoolspoetryPyInstaller。理解这些工具对于构建可维护、可分发的 Python 项目至关重要。

一、setuptools:Python 打包的基石

setuptools 是 Python 打包的核心库,它扩展了 Python 的 distutils,提供了更为强大和灵活的打包机制。 使用 setuptools,我们可以定义项目元数据、依赖关系,并生成各种格式的分发包。

1.1 核心概念:setup.py

setuptools 的核心在于 setup.py 文件。 这个文件定义了项目的构建和安装过程。 让我们看一个简单的例子:

# setup.py
from setuptools import setup, find_packages

setup(
    name='my_package',
    version='0.1.0',
    packages=find_packages(),  # 自动查找项目中的所有包
    install_requires=[
        'requests',
        'numpy',
    ],
    entry_points={
        'console_scripts': [
            'my_script = my_package.my_module:main',
        ],
    },
    author='Your Name',
    author_email='[email protected]',
    description='A short description of your package',
    license='MIT',
    url='https://github.com/your_username/my_package',
    classifiers=[
        'Development Status :: 3 - Alpha',
        'Intended Audience :: Developers',
        'License :: OSI Approved :: MIT License',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.6',
        'Programming Language :: Python :: 3.7',
        'Programming Language :: Python :: 3.8',
        'Programming Language :: Python :: 3.9',
    ],
)
  • name: 包的名称,这是在 PyPI 上注册的名称。
  • version: 包的版本号,遵循语义化版本控制(Semantic Versioning)。
  • packages: 列出要包含在包中的所有 Python 包。 find_packages() 函数会自动找到项目中的所有包。
  • install_requires: 指定项目运行时所需的依赖项。 在安装包时,pip 会自动安装这些依赖项。
  • entry_points: 定义可执行脚本,允许将 Python 函数暴露为命令行工具。 在上面的例子中,my_script 命令会执行 my_package.my_module 模块中的 main 函数。
  • author, author_email, description, license, url: 项目的元数据,用于在 PyPI 上显示项目信息。
  • classifiers: 用于对包进行分类,方便在 PyPI 上搜索。

1.2 主要命令

setuptools 提供了许多命令来构建和分发包。 这些命令通常通过 python setup.py <command> 的方式执行。

命令 描述
install 安装包及其依赖项。
develop 以“开发模式”安装包。 这会在 site-packages 目录中创建一个指向项目源代码的链接,方便开发和调试。
sdist 创建源代码分发包(.tar.gz)。
bdist_wheel 创建 wheel 分发包(.whl)。 wheel 是一种二进制包格式,安装速度更快,更可靠。
bdist_egg 创建 egg 分发包(.egg)。 已过时,推荐使用 wheel
register 在 PyPI 上注册包。 需要 PyPI 账户和密码
upload 将包上传到 PyPI。 需要 PyPI 账户和密码
build_ext --inplace 在当前目录构建 C 扩展模块。 这对于开发需要编译的模块很有用。

1.3 示例:构建和安装包

假设我们有以下项目结构:

my_package/
├── my_package/
│   ├── __init__.py
│   ├── my_module.py
│   └── another_module.py
├── setup.py

my_module.py 的内容如下:

# my_package/my_module.py
def main():
    print("Hello from my_package!")

if __name__ == "__main__":
    main()

现在,我们可以使用以下命令来构建和安装包:

  1. 创建源代码分发包:

    python setup.py sdist

    这会在 dist 目录中创建一个 my_package-0.1.0.tar.gz 文件。

  2. 创建 wheel 分发包:

    python setup.py bdist_wheel

    这会在 dist 目录中创建一个 my_package-0.1.0-py3-none-any.whl 文件。

  3. 安装包:

    pip install .  # 从当前目录安装

    或者,安装 wheel 文件:

    pip install dist/my_package-0.1.0-py3-none-any.whl
  4. 运行命令行脚本:

    安装完成后,就可以在命令行中运行 my_script 命令:

    my_script

    输出:

    Hello from my_package!

1.4 MANIFEST.in:包含非 Python 文件

setuptools 默认只包含 Python 文件。 如果项目需要包含其他类型的文件(例如,配置文件、数据文件、静态资源),则需要创建一个 MANIFEST.in 文件来指定这些文件。

例如,如果想包含 data 目录中的所有文件,可以在 MANIFEST.in 中添加以下行:

include data/*

然后,需要在 setup.py 中添加 include_package_data=True

# setup.py
from setuptools import setup, find_packages

setup(
    name='my_package',
    version='0.1.0',
    packages=find_packages(),
    install_requires=[
        'requests',
        'numpy',
    ],
    include_package_data=True,  # 包含 MANIFEST.in 中指定的文件
    # ...
)

二、Poetry:依赖管理与打包的现代解决方案

Poetry 是一个现代的 Python 依赖管理和打包工具。 它旨在简化依赖管理,提供一致且可重复的构建过程。 Poetry 解决了 setuptools 在依赖管理方面的一些痛点,例如版本冲突和环境隔离。

2.1 核心概念:pyproject.toml

Poetry 使用 pyproject.toml 文件来管理项目元数据、依赖关系和构建配置。 这是一个 TOML 格式的文件,易于阅读和编辑。

# pyproject.toml
[tool.poetry]
name = "my_package"
version = "0.1.0"
description = "A short description of my package"
authors = ["Your Name <[email protected]>"]
license = "MIT"
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.6"
requests = "^2.25.1"
numpy = "^1.20.0"

[tool.poetry.dev-dependencies]
pytest = "^6.2.4"
flake8 = "^3.9.2"

[tool.poetry.scripts]
my_script = "my_package.my_module:main"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
  • [tool.poetry]: 定义项目的元数据,例如名称、版本、描述、作者和许可证。
  • [tool.poetry.dependencies]: 指定项目运行时所需的依赖项。 ^ 符号表示兼容的版本范围。 例如,requests = "^2.25.1" 表示允许安装 2.25.1 及更高版本的 requests,但不包括 3.0.0 及更高版本。
  • [tool.poetry.dev-dependencies]: 指定开发时所需的依赖项,例如测试框架和代码检查工具。
  • [tool.poetry.scripts]: 定义可执行脚本,与 setuptoolsentry_points 类似。
  • [build-system]: 指定用于构建项目的构建系统。 Poetry 使用 poetry-core 作为构建后端。

2.2 主要命令

Poetry 提供了丰富的命令来管理项目依赖关系、构建和发布包。

命令 描述
install 安装项目依赖项。 Poetry 会创建一个虚拟环境,并将所有依赖项安装到该环境中。
add pyproject.toml 文件添加新的依赖项。
remove pyproject.toml 文件中移除依赖项。
update 更新项目依赖项到最新版本。
build 构建包。 Poetry 会创建源代码分发包(.tar.gz)和 wheel 分发包(.whl)。
publish 将包发布到 PyPI。 需要 PyPI 账户和密码
run 在项目的虚拟环境中运行命令。 例如,poetry run python my_script.py
shell 激活项目的虚拟环境。
show 显示已安装的依赖项。
lock 创建 poetry.lock 文件,记录项目的确切依赖项版本。 这可以确保项目在不同环境中具有一致的依赖关系。

2.3 示例:使用 Poetry 构建和发布包

  1. 创建新项目:

    poetry new my_package
    cd my_package

    这会创建一个包含 pyproject.toml 文件和基本项目结构的目录。

  2. 添加依赖项:

    poetry add requests
    poetry add --dev pytest flake8

    这会将 requests 添加到运行时依赖项,并将 pytestflake8 添加到开发时依赖项。

  3. 安装依赖项:

    poetry install

    这会创建一个虚拟环境并将所有依赖项安装到该环境中。

  4. 构建包:

    poetry build

    这会在 dist 目录中创建源代码分发包和 wheel 分发包。

  5. 发布包:

    poetry publish

    这会将包发布到 PyPI。 需要配置 PyPI 凭据。 可以使用 poetry config pypi-token.pypi <YOUR_PYPI_TOKEN> 命令配置 token。

2.4 poetry.lock:锁定依赖关系

poetry.lock 文件记录了项目中所有依赖项的确切版本。 当运行 poetry install 命令时,Poetry 会首先检查 poetry.lock 文件。 如果该文件存在,Poetry 会安装 poetry.lock 文件中指定的版本,而不是使用 pyproject.toml 文件中指定的版本范围。

这可以确保项目在不同环境中具有一致的依赖关系。 建议将 poetry.lock 文件提交到版本控制系统。

三、PyInstaller:将 Python 应用打包成独立可执行文件

PyInstaller 是一个强大的工具,可以将 Python 应用打包成独立的可执行文件,无需安装 Python 解释器或任何依赖项即可运行。 这对于分发桌面应用程序非常有用。

3.1 核心概念:单文件模式 vs. 目录模式

PyInstaller 提供了两种主要的打包模式:

  • 单文件模式 (--onefile): 将所有依赖项和 Python 代码打包成一个单独的可执行文件。 这使得分发非常方便,但启动速度可能会稍慢。
  • 目录模式 (--onedir): 将所有依赖项和 Python 代码打包到一个目录中。 可执行文件位于该目录中。 启动速度更快,但需要分发整个目录。

3.2 基本用法

pyinstaller <your_script.py>

这会将 your_script.py 打包成可执行文件,默认使用目录模式。

3.3 常用选项

选项 描述
--onefile 使用单文件模式。
--onedir 使用目录模式(默认)。
--name <name> 指定可执行文件的名称。
--icon <icon> 指定可执行文件的图标。
--noconsole 隐藏控制台窗口。 适用于 GUI 应用程序。
--windowed --noconsole 相同,隐藏控制台窗口。
--add-data <src:dest> 添加非 Python 文件到打包后的程序。 src 是源文件或目录,dest 是目标位置(相对于可执行文件)。
--add-binary <src:dest> 添加二进制文件到打包后的程序,例如 DLL 文件。 src 是源文件或目录,dest 是目标位置(相对于可执行文件)。
--hidden-import <module> 强制包含某个模块。 当 PyInstaller 无法自动检测到某个模块时,可以使用此选项。
--exclude-module <module> 排除某个模块。
--upx-dir <path> 指定 UPX 可执行文件的路径。 UPX 可以压缩可执行文件,减小文件大小。 需要单独安装 UPX。
--runtime-hook <file> 指定运行时 hook 脚本。 可以在程序启动时执行自定义代码。
--splash <image> 指定启动画面图像。

3.4 示例:打包一个简单的 GUI 应用

假设我们有一个使用 tkinter 编写的 GUI 应用 my_gui.py

# my_gui.py
import tkinter as tk

def main():
    root = tk.Tk()
    root.title("My GUI App")

    label = tk.Label(root, text="Hello, Tkinter!")
    label.pack()

    root.mainloop()

if __name__ == "__main__":
    main()

我们可以使用以下命令将其打包成一个没有控制台窗口的单文件可执行文件:

pyinstaller --onefile --noconsole --name my_gui my_gui.py

这会在 dist 目录中创建一个 my_gui.exe 文件。 双击该文件即可运行 GUI 应用。

3.5 处理依赖关系和数据文件

PyInstaller 能够自动检测大多数依赖项。 但是,有时可能需要手动指定一些依赖项或数据文件。

  • --hidden-import: 如果 PyInstaller 无法自动检测到某个模块,可以使用 --hidden-import 选项强制包含该模块。 例如:

    pyinstaller --onefile --hidden-import my_module my_script.py
  • --add-data: 可以使用 --add-data 选项添加非 Python 文件到打包后的程序。 例如,如果需要包含一个名为 config.txt 的配置文件:

    pyinstaller --onefile --add-data "config.txt:." my_script.py

    这会将 config.txt 文件复制到可执行文件所在的目录中。 在 Python 代码中,可以使用 os.path.dirname(sys.executable) 获取可执行文件所在的目录,然后使用 os.path.join() 构造 config.txt 的完整路径。

    # my_script.py
    import os
    import sys
    
    def get_config_path():
        if hasattr(sys, '_MEIPASS'):
            # Running in a PyInstaller bundle
            return os.path.join(sys._MEIPASS, "config.txt")
        else:
            # Running in a normal Python environment
            return "config.txt"
    
    config_path = get_config_path()
    print(f"Config path: {config_path}")
  • --add-binary: 与 --add-data 类似,但用于添加二进制文件。

3.6 使用 .spec 文件进行高级配置

对于复杂的项目,可以使用 .spec 文件进行更高级的配置。 .spec 文件是一个 Python 脚本,用于定义 PyInstaller 的构建过程。

可以使用以下命令生成 .spec 文件:

pyi-makespec <your_script.py>

然后,可以编辑 .spec 文件以自定义构建过程。 例如,可以修改 datas 列表以添加数据文件:

# my_script.spec
# -*- mode: python ; coding: utf-8 -*-

block_cipher = None

a = Analysis(['my_script.py'],
             pathex=[],
             binaries=[],
             datas=[('config.txt', '.')], # 添加 config.txt 文件
             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,
          a.binaries,
          a.zipfiles,
          a.datas,
          [],
          name='my_script',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          runtime_tmpdir=None,
          console=True,
          disable_windowed_traceback=False,
          target_arch=None,
          codesign_identity=None,
          entitlements_file=None )

修改 .spec 文件后,可以使用以下命令构建可执行文件:

pyinstaller my_script.spec

四、选择合适的工具

工具 优点 缺点 适用场景
setuptools Python 打包的基础,广泛支持,功能强大。 依赖管理较为复杂,版本冲突问题需要手动解决。 简单的 Python 包,不需要复杂的依赖管理。
Poetry 现代化的依赖管理和打包工具,简化依赖管理,提供一致的构建过程。 学习曲线稍陡峭,对于非常老的项目迁移成本较高。 需要管理复杂依赖关系的 Python 包,注重构建过程的可重复性。
PyInstaller 将 Python 应用打包成独立可执行文件,无需安装 Python 解释器或任何依赖项即可运行。 打包后的文件较大,启动速度可能较慢,对于复杂的应用需要手动配置依赖关系。 需要分发桌面应用程序,无需用户安装 Python 环境。

总结:工具选择与应用场景

掌握 setuptoolspoetryPyInstaller 是 Python 开发者的重要技能。 setuptools 是打包的基础,poetry 简化了依赖管理,而 PyInstaller 可以将 Python 应用打包成独立的可执行文件。 根据项目的需求选择合适的工具,可以提高开发效率,并更好地分发 Python 应用。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注