好的,下面是一篇关于 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 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.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
适用于创建独立的可执行文件。 选择哪个工具取决于你的具体需求。