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

好的,下面是一篇关于 Python 打包与分发的文章,内容涵盖 setuptoolspoetryPyInstaller 的用法,并以讲座的形式呈现。

Python 打包与分发:setuptoolspoetryPyInstaller的用法

各位同学,大家好!今天我们来聊一聊 Python 项目的打包与分发。这是一个非常重要的环节,它关系到你的代码如何让其他人使用,如何部署到生产环境。我们会重点介绍三个工具:setuptoolspoetryPyInstaller。它们分别解决不同的问题,适用于不同的场景。

一、setuptools:构建包的基础

setuptools 是 Python 打包的基石。它是一个用于构建、打包和分发 Python 包的标准库。虽然现在有了更现代的工具,但了解 setuptools 仍然很重要,因为很多项目仍然依赖它。

1.1 核心概念

  • setup.py: 这是 setuptools 的核心文件,它描述了你的包的信息,比如名称、版本、依赖等。
  • MANIFEST.in: 这个文件指定了哪些非 Python 文件(比如数据文件、配置文件)应该包含在你的包中。
  • setup() 函数: setup.py 中必须包含一个 setup() 函数,它接受一系列参数,用于定义包的元数据。

1.2 setup.py 的基本结构

一个典型的 setup.py 文件可能如下所示:

from setuptools import setup, find_packages

setup(
    name='my_package',
    version='0.1.0',
    packages=find_packages(exclude=['tests*']),  # 自动查找包含 __init__.py 的包
    install_requires=[
        'requests',
        'numpy>=1.18',
    ],
    entry_points={
        'console_scripts': [
            'my_command = my_package.module:main_function', # 创建命令行工具
        ],
    },
    author='Your Name',
    author_email='[email protected]',
    description='A short description of your package',
    long_description=open('README.md').read(),  # 最好使用 reStructuredText 格式
    long_description_content_type='text/markdown', # 指定 Markdown 格式
    url='https://github.com/yourusername/my_package',
    license='MIT',
    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',
    ],
    python_requires='>=3.6', # 指定 Python 版本
    include_package_data=True, # 包含 MANIFEST.in 中指定的文件
)

解释:

  • name: 包的名称。
  • version: 包的版本。
  • packages: 使用 find_packages() 自动查找项目中的包。exclude 参数可以排除不需要包含的包,例如测试相关的包。
  • install_requires: 指定你的包依赖的其他 Python 包。 >=1.18 表示版本大于等于 1.18。
  • entry_points: 用于创建命令行工具。 my_command 是命令名称,my_package.module:main_function 指定了执行命令时要调用的函数。
  • author, author_email, description, long_description, url, license: 包的作者、描述、URL 和许可证信息。
  • classifiers: 用于 PyPI(Python Package Index)对你的包进行分类。
  • python_requires: 指定你的包兼容的 Python 版本。
  • include_package_data: 设置为 True 时,会包含 MANIFEST.in 文件中指定的文件。

1.3 MANIFEST.in 的使用

MANIFEST.in 用于指定除了 Python 模块之外,还需要包含在包中的文件。例如,你的项目可能包含配置文件、数据文件、模板文件等。

一个 MANIFEST.in 文件的例子:

include README.md
include LICENSE
recursive-include my_package/data *
recursive-include my_package/templates *

解释:

  • include README.md: 包含 README.md 文件。
  • recursive-include my_package/data *: 递归地包含 my_package/data 目录下的所有文件。
  • recursive-include my_package/templates *: 递归地包含 my_package/templates 目录下的所有文件。

1.4 打包和分发

  1. 创建源码发行版 (source distribution):

    python setup.py sdist

    这会在 dist 目录下创建一个 .tar.gz 文件,包含了你的项目源代码和 setup.py 等文件。

  2. 创建 wheel 发行版 (wheel distribution):

    python setup.py bdist_wheel

    这会在 dist 目录下创建一个 .whl 文件,它是预编译的二进制包,安装速度更快。 需要先安装 wheel 库: pip install wheel

  3. 上传到 PyPI:

    首先你需要注册一个 PyPI 账号,并安装 twine

    pip install twine

    然后使用 twine 上传你的包:

    twine upload dist/*

    twine 会提示你输入 PyPI 的用户名和密码。

1.5 setuptools 的缺点

  • 依赖管理: setuptools 的依赖管理比较弱,需要手动维护 install_requires,容易出错。
  • 版本冲突: 在复杂项目中,容易出现依赖版本冲突的问题。
  • 缺乏虚拟环境管理: setuptools 本身不提供虚拟环境管理功能。

二、poetry:现代化的依赖管理和打包工具

poetry 是一个现代化的 Python 依赖管理和打包工具,它解决了 setuptools 的一些痛点。它使用 pyproject.toml 文件来管理项目依赖,并提供虚拟环境管理、依赖解析等功能。

2.1 pyproject.toml 的基本结构

poetry 使用 pyproject.toml 文件来替代 setup.pyrequirements.txt。一个典型的 pyproject.toml 文件如下所示:

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

[tool.poetry.dependencies]
python = "^3.6"
requests = "^2.25"
numpy = ">=1.18,<1.23"

[tool.poetry.dev-dependencies]
pytest = "^6.0"
flake8 = "^3.9"

[tool.poetry.scripts]
my_command = "my_package.module:main_function"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

解释:

  • [tool.poetry]: 包含包的基本信息,如名称、版本、描述、作者、许可证和 README 文件。
  • [tool.poetry.dependencies]: 指定生产环境依赖。 ^2.25 表示兼容 2.25 及以上版本,但不兼容主版本号不同的版本(例如 3.0)。 >=1.18,<1.23 表示版本大于等于 1.18 且小于 1.23。
  • [tool.poetry.dev-dependencies]: 指定开发环境依赖,如测试框架、代码检查工具等。
  • [tool.poetry.scripts]: 定义命令行工具。
  • [build-system]: 指定构建系统,poetry 使用 poetry-core 作为构建后端。

2.2 poetry 的常用命令

  • 创建项目:

    poetry new my_package

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

  • 添加依赖:

    poetry add requests
    poetry add --dev pytest

    poetry add 用于添加生产环境依赖,poetry add --dev 用于添加开发环境依赖。

  • 安装依赖:

    poetry install

    这会根据 pyproject.toml 文件安装所有依赖。 poetry 会自动创建和管理虚拟环境。

  • 运行脚本:

    poetry run python my_package/module.py

    这会在 poetry 创建的虚拟环境中运行 Python 脚本。

  • 构建包:

    poetry build

    这会在 dist 目录下创建 .tar.gz.whl 文件。

  • 发布包:

    poetry publish

    这会将你的包发布到 PyPI。 你需要配置 PyPI 的 API token。

2.3 虚拟环境管理

poetry 自动管理虚拟环境,你不需要手动创建和激活虚拟环境。 poetry 会在项目目录下创建一个 .venv 目录,用于存储虚拟环境。 你可以使用 poetry env info 命令查看虚拟环境的信息。

2.4 依赖解析

poetry 使用一个复杂的依赖解析算法,可以自动解决依赖冲突,确保你的项目可以正常运行。 poetry.lock 文件记录了所有依赖的确切版本,可以保证在不同的环境中安装相同的依赖。

2.5 从 setuptools 迁移到 poetry

如果你已经使用 setuptools,可以逐步迁移到 poetry。 首先,创建一个 pyproject.toml 文件,并将 setup.py 中的信息复制到 pyproject.toml 中。 然后,使用 poetry install 安装依赖。 最后,删除 setup.pyrequirements.txt 文件。

2.6 poetry 的优势

  • 依赖管理: poetry 提供了强大的依赖管理功能,可以自动解决依赖冲突。
  • 虚拟环境管理: poetry 自动创建和管理虚拟环境。
  • 易用性: poetry 的命令简洁易懂,使用起来非常方便。
  • 标准化: poetry 使用 pyproject.toml 文件,符合 PEP 518 标准。

三、PyInstaller:将 Python 代码打包成独立的可执行文件

setuptoolspoetry 主要用于打包 Python 包,以便其他人可以安装和使用你的代码。 但有时候,你可能需要将你的 Python 代码打包成一个独立的可执行文件,这样用户无需安装 Python 解释器和任何依赖,就可以直接运行你的程序。 PyInstaller 就是一个用于实现这个目标的工具。

3.1 核心概念

  • 单文件模式: 将所有依赖打包成一个单独的可执行文件。
  • 单目录模式: 将所有依赖打包到一个目录中,包含一个可执行文件和一些动态链接库。
  • Spec 文件: 一个配置文件,用于控制 PyInstaller 的打包过程。

3.2 基本用法

  1. 安装 PyInstaller:

    pip install pyinstaller
  2. 打包你的 Python 脚本:

    pyinstaller your_script.py

    这会在 dist 目录下创建一个可执行文件,默认是单目录模式。

  3. 使用单文件模式:

    pyinstaller --onefile your_script.py

    这会在 dist 目录下创建一个单独的可执行文件。

3.3 常用选项

  • --onefile: 创建单文件可执行文件。
  • --onedir: 创建单目录可执行文件。
  • --windowed--noconsole: 创建 GUI 程序时,不显示控制台窗口。
  • --name: 指定可执行文件的名称。
  • --icon: 指定可执行文件的图标。
  • --add-data: 添加数据文件到可执行文件中。
  • --add-binary: 添加二进制文件到可执行文件中。
  • --hidden-import: 强制 PyInstaller 包含某些模块。

3.4 Spec 文件的使用

Spec 文件是一个 Python 脚本,用于控制 PyInstaller 的打包过程。 你可以使用 pyi-makespec 命令生成一个 Spec 文件:

pyi-makespec your_script.py

这会创建一个 your_script.spec 文件。 你可以编辑这个文件,修改打包选项。 然后使用以下命令进行打包:

pyinstaller your_script.spec

3.5 添加数据文件

如果你的程序需要读取数据文件,可以使用 --add-data 选项将数据文件添加到可执行文件中。 例如:

pyinstaller --add-data "data.txt:." your_script.py

这会将 data.txt 文件添加到可执行文件的根目录下。 在程序中,你可以使用 sys._MEIPASS 环境变量来获取可执行文件的根目录:

import sys, os

if hasattr(sys, '_MEIPASS'):
    base_path = sys._MEIPASS
else:
    base_path = os.path.abspath(".")

data_file = os.path.join(base_path, "data.txt")

with open(data_file, "r") as f:
    data = f.read()

3.6 解决隐藏依赖问题

有时候,PyInstaller 可能无法自动检测到所有的依赖,导致程序运行时出错。 你可以使用 --hidden-import 选项强制 PyInstaller 包含某些模块。 例如:

pyinstaller --hidden-import "my_module" your_script.py

你也可以在 Spec 文件中添加 hiddenimports 选项:

a = Analysis(['your_script.py'],
             pathex=['.'],
             binaries=[],
             datas=[],
             hiddenimports=['my_module'],
             hookspath=[],
             hooksconfig={},
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=None,
             noarchive=False)

3.7 PyInstaller 的局限性

  • 文件大小: 打包后的可执行文件通常比较大,因为它包含了 Python 解释器和所有依赖。
  • 启动速度: 打包后的程序启动速度可能比较慢,因为它需要解压缩文件。
  • 反编译: 虽然 PyInstaller 可以将 Python 代码打包成可执行文件,但仍然存在被反编译的风险。

四、工具对比总结

为了更清晰地了解这三个工具的特点,我们用一个表格来进行对比:

特性 setuptools poetry PyInstaller
主要用途 打包 Python 包 依赖管理和打包 Python 包 将 Python 代码打包成独立的可执行文件
依赖管理 手动维护 install_requires 自动解决依赖冲突,使用 poetry.lock 文件 不负责依赖管理,需要手动安装依赖
虚拟环境管理 不提供 自动创建和管理虚拟环境 不提供
配置文件 setup.py, MANIFEST.in pyproject.toml Spec 文件
输出格式 .tar.gz, .whl .tar.gz, .whl 可执行文件(单文件或单目录)
适用场景 发布 Python 包到 PyPI 开发和发布现代化的 Python 项目 需要将 Python 代码打包成独立的可执行文件,方便用户使用
优点 简单易用,使用广泛 依赖管理强大,虚拟环境管理方便,易于上手 可以创建独立的可执行文件,无需安装 Python
缺点 依赖管理弱,容易出现版本冲突,缺乏虚拟环境管理 相对较新,学习曲线稍陡 文件大小大,启动速度慢,存在反编译风险

五、实际案例分析

为了更好地理解这些工具的用法,我们来看一个实际的案例。假设我们要开发一个简单的命令行工具,用于从网上下载文件。

  1. 使用 poetry 创建项目:

    poetry new downloader
    cd downloader
  2. 编写代码:

    downloader/downloader.py 文件中编写以下代码:

    import requests
    import argparse
    import os
    
    def download_file(url, output_path):
        try:
            response = requests.get(url, stream=True)
            response.raise_for_status()  # 检查请求是否成功
    
            total_size_in_bytes = int(response.headers.get('content-length', 0))
            block_size = 1024  # 1 Kibibyte
            with open(output_path, 'wb') as file:
                for data in response.iter_content(block_size):
                    file.write(data)
            print(f"Downloaded '{url}' to '{output_path}'")
    
        except requests.exceptions.RequestException as e:
            print(f"Error downloading '{url}': {e}")
    
    def main():
        parser = argparse.ArgumentParser(description="Download files from the internet.")
        parser.add_argument("url", help="The URL of the file to download.")
        parser.add_argument("-o", "--output", help="The output path for the downloaded file.", default="downloaded_file")
    
        args = parser.parse_args()
    
        download_file(args.url, args.output)
    
    if __name__ == "__main__":
        main()
  3. 添加依赖:

    poetry add requests
  4. 配置 pyproject.toml:

    修改 pyproject.toml 文件,添加以下内容:

    [tool.poetry.scripts]
    downloader = "downloader.downloader:main"
  5. 构建和发布:

    poetry build
    poetry publish
  6. 使用 PyInstaller 打包:

    pyinstaller --onefile --name downloader downloader/downloader.py

    这会在 dist 目录下创建一个名为 downloader 的可执行文件。

总结

今天我们学习了 Python 的打包与分发,重点介绍了 setuptoolspoetryPyInstaller 三个工具。 setuptools 是基础,poetry 是现代化的选择,而 PyInstaller 适用于创建独立的可执行文件。 选择哪个工具取决于你的具体需求。

发表回复

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