好的,下面是一篇关于 Python 打包与分发的文章,内容涵盖 setuptools、poetry 和 PyInstaller 的用法,并以讲座的形式呈现。
Python 打包与分发:setuptools、poetry和PyInstaller的用法
各位同学,大家好!今天我们来聊一聊 Python 项目的打包与分发。这是一个非常重要的环节,它关系到你的代码如何让其他人使用,如何部署到生产环境。我们会重点介绍三个工具:setuptools、poetry 和 PyInstaller。它们分别解决不同的问题,适用于不同的场景。
一、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 打包和分发
-
创建源码发行版 (source distribution):
python setup.py sdist这会在
dist目录下创建一个.tar.gz文件,包含了你的项目源代码和setup.py等文件。 -
创建 wheel 发行版 (wheel distribution):
python setup.py bdist_wheel这会在
dist目录下创建一个.whl文件,它是预编译的二进制包,安装速度更快。 需要先安装wheel库:pip install wheel -
上传到 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.py 和 requirements.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 pytestpoetry 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.py 和 requirements.txt 文件。
2.6 poetry 的优势
- 依赖管理:
poetry提供了强大的依赖管理功能,可以自动解决依赖冲突。 - 虚拟环境管理:
poetry自动创建和管理虚拟环境。 - 易用性:
poetry的命令简洁易懂,使用起来非常方便。 - 标准化:
poetry使用pyproject.toml文件,符合 PEP 518 标准。
三、PyInstaller:将 Python 代码打包成独立的可执行文件
setuptools 和 poetry 主要用于打包 Python 包,以便其他人可以安装和使用你的代码。 但有时候,你可能需要将你的 Python 代码打包成一个独立的可执行文件,这样用户无需安装 Python 解释器和任何依赖,就可以直接运行你的程序。 PyInstaller 就是一个用于实现这个目标的工具。
3.1 核心概念
- 单文件模式: 将所有依赖打包成一个单独的可执行文件。
- 单目录模式: 将所有依赖打包到一个目录中,包含一个可执行文件和一些动态链接库。
- Spec 文件: 一个配置文件,用于控制
PyInstaller的打包过程。
3.2 基本用法
-
安装
PyInstaller:pip install pyinstaller -
打包你的 Python 脚本:
pyinstaller your_script.py这会在
dist目录下创建一个可执行文件,默认是单目录模式。 -
使用单文件模式:
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 |
| 缺点 | 依赖管理弱,容易出现版本冲突,缺乏虚拟环境管理 | 相对较新,学习曲线稍陡 | 文件大小大,启动速度慢,存在反编译风险 |
五、实际案例分析
为了更好地理解这些工具的用法,我们来看一个实际的案例。假设我们要开发一个简单的命令行工具,用于从网上下载文件。
-
使用
poetry创建项目:poetry new downloader cd downloader -
编写代码:
在
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() -
添加依赖:
poetry add requests -
配置
pyproject.toml:修改
pyproject.toml文件,添加以下内容:[tool.poetry.scripts] downloader = "downloader.downloader:main" -
构建和发布:
poetry build poetry publish -
使用
PyInstaller打包:pyinstaller --onefile --name downloader downloader/downloader.py这会在
dist目录下创建一个名为downloader的可执行文件。
总结
今天我们学习了 Python 的打包与分发,重点介绍了 setuptools、poetry 和 PyInstaller 三个工具。 setuptools 是基础,poetry 是现代化的选择,而 PyInstaller 适用于创建独立的可执行文件。 选择哪个工具取决于你的具体需求。