好的,咱们今天来聊聊 Python 打包这事儿,特别是那些带 C 扩展的复杂包。别担心,我会尽量用大白话,争取让大家听明白,搞懂怎么用 setuptools
和 distutils
把你的代码打包成一个能让别人轻松安装的宝贝。
开场白:为什么要打包?
想象一下,你辛辛苦苦写了一个 Python 库,里面用 C 优化了一些关键部分,性能嗷嗷叫。现在你想分享给你的小伙伴或者发布到 PyPI 上,让全世界的人都能用。难道你要把你的代码一股脑地扔给他们,然后说:“自己编译去吧!”?
这显然不靠谱。
打包就是为了解决这个问题。它可以把你的代码、依赖、编译好的 C 扩展等等,都打包成一个标准格式的文件(比如 .whl
或者 .tar.gz
),然后别人只需要用 pip install
一下,就能轻松安装你的库,不用操心编译、依赖这些乱七八糟的事情。
distutils
和 setuptools
:一对好基友
distutils
是 Python 官方提供的打包工具,历史悠久,地位崇高。但是,它功能比较简单,只能满足一些基本的打包需求。
setuptools
是一个第三方库,它在 distutils
的基础上进行了扩展,提供了更多更强大的功能,比如:
- 依赖管理:自动下载和安装依赖包
- 插件机制:允许其他库扩展打包过程
- 自动发现包:自动找到你的 Python 模块和包
- 支持 C 扩展:方便地编译和打包 C 扩展
现在基本上大家都用 setuptools
,因为它更方便、更强大。所以,咱们今天主要讲 setuptools
,但也会简单提一下 distutils
,毕竟它们是好基友嘛。
setup.py
:打包的灵魂
setup.py
是你打包项目的核心文件。它告诉 setuptools
你的项目是什么、有哪些文件、需要哪些依赖等等。
一个最简单的 setup.py
可能是这样的:
from setuptools import setup
setup(
name='my_package',
version='0.1.0',
packages=['my_package'],
)
这个 setup.py
定义了一个名为 my_package
的包,版本号是 0.1.0
,并且包含一个名为 my_package
的 Python 包。
setup()
函数:配置你的包
setup()
函数是 setup.py
的核心。它接受很多参数,用于配置你的包。下面是一些常用的参数:
name
: 包的名字。version
: 包的版本号。packages
: 一个包含所有 Python 包的列表。py_modules
: 一个包含所有 Python 模块的列表(单个.py
文件)。install_requires
: 一个包含所有依赖包的列表。author
: 作者的名字。author_email
: 作者的邮箱。description
: 包的简短描述。long_description
: 包的详细描述(通常从 README 文件读取)。url
: 包的网站地址。classifiers
: 一系列分类器,用于描述你的包的特性(比如编程语言、操作系统、许可证等等)。ext_modules
: 一个包含所有 C 扩展的列表(后面会详细讲)。entry_points
: 定义命令行工具或者其他入口点。
一个更完整的例子
from setuptools import setup, find_packages
with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()
setup(
name="my_package",
version="0.1.0",
author="Your Name",
author_email="[email protected]",
description="A short description of your package",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/yourusername/my_package",
packages=find_packages(),
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
python_requires='>=3.6',
install_requires=[
"requests",
"numpy",
],
)
这个例子使用了 find_packages()
函数自动查找项目中的所有 Python 包,并从 README.md
文件读取详细描述。它还指定了依赖包 requests
和 numpy
,以及一些分类器。
C 扩展:让你的 Python 飞起来
C 扩展是用 C 或者 C++ 编写的 Python 模块。它们可以用来优化 Python 代码的性能,或者访问一些 Python 无法直接访问的底层资源。
要构建 C 扩展,你需要使用 setuptools
提供的 Extension
类。
from setuptools import setup, Extension
module1 = Extension('my_package.my_module',
sources=['my_package/my_module.c'])
setup(
name='my_package',
version='0.1.0',
packages=['my_package'],
ext_modules=[module1],
)
这个例子定义了一个名为 my_package.my_module
的 C 扩展,它的源代码是 my_package/my_module.c
。
Extension
类:配置 C 扩展
Extension
类接受很多参数,用于配置 C 扩展。下面是一些常用的参数:
name
: 扩展的名字(必须包含包名)。sources
: 一个包含所有 C/C++ 源代码文件的列表。include_dirs
: 一个包含所有头文件目录的列表。define_macros
: 一个包含所有宏定义的列表(用于条件编译)。library_dirs
: 一个包含所有库文件目录的列表。libraries
: 一个包含所有需要链接的库的列表。extra_compile_args
: 一个包含所有额外的编译选项的列表。extra_link_args
: 一个包含所有额外的链接选项的列表。
一个更复杂的 C 扩展例子
假设你的 C 扩展依赖于一个外部库 libfoo
,并且需要使用一些特定的编译选项。
from setuptools import setup, Extension
module1 = Extension('my_package.my_module',
sources=['my_package/my_module.c'],
include_dirs=['/usr/local/include'],
libraries=['foo'],
library_dirs=['/usr/local/lib'],
extra_compile_args=['-O3', '-Wall'],
extra_link_args=['-pthread'])
setup(
name='my_package',
version='0.1.0',
packages=['my_package'],
ext_modules=[module1],
)
这个例子指定了头文件目录 /usr/local/include
,库文件目录 /usr/local/lib
,需要链接的库 libfoo
,以及一些额外的编译和链接选项。
MANIFEST.in
:包含额外文件
有时候,你的包除了 Python 代码和 C 扩展之外,还包含一些其他文件,比如配置文件、数据文件、文档等等。你需要使用 MANIFEST.in
文件告诉 setuptools
这些文件也需要包含在包里。
MANIFEST.in
文件是一个简单的文本文件,每行指定一个文件或者目录。
include my_package/data.txt
recursive-include my_package/docs *
global-exclude *.pyc
这个例子包含了 my_package/data.txt
文件,递归包含了 my_package/docs
目录下的所有文件,并且排除了所有 .pyc
文件。
构建你的包
有了 setup.py
和 MANIFEST.in
,你就可以构建你的包了。打开终端,进入你的项目目录,然后运行以下命令:
python setup.py sdist bdist_wheel
这个命令会生成两个文件:
sdist
: 源代码包(通常是一个.tar.gz
文件)。bdist_wheel
: wheel 包(通常是一个.whl
文件)。
wheel 包是一种二进制包,它已经包含了编译好的 C 扩展,所以安装速度更快。
安装你的包
你可以使用 pip
安装你的包:
pip install dist/my_package-0.1.0-py3-none-any.whl
或者,如果你想从源代码安装,可以这样做:
pip install dist/my_package-0.1.0.tar.gz
pyproject.toml
:新的打包标准
pyproject.toml
是一个新的打包配置文件,它正在逐渐取代 setup.py
。它使用 TOML 格式,更加清晰易懂。
一个简单的 pyproject.toml
文件可能是这样的:
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "my_package"
version = "0.1.0"
authors = [
{ name = "Your Name", email = "[email protected]" },
]
description = "A short description of your package"
readme = "README.md"
requires-python = ">=3.6"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
dependencies = [
"requests",
"numpy",
]
[project.urls]
"Homepage" = "https://github.com/yourusername/my_package"
"Bug Tracker" = "https://github.com/yourusername/my_package/issues"
要使用 pyproject.toml
,你需要安装 build
包:
pip install build
然后,你可以使用 build
命令构建你的包:
python -m build
总结:打包,让你的代码飞得更高
打包是 Python 开发中非常重要的一环。它可以让你轻松地分享你的代码,让别人也能享受到你的劳动成果。
setuptools
是一个强大的打包工具,它提供了很多功能,可以满足各种各样的打包需求。setup.py
是打包的核心文件,它告诉setuptools
你的项目是什么、有哪些文件、需要哪些依赖等等。- C 扩展可以用来优化 Python 代码的性能,或者访问一些 Python 无法直接访问的底层资源。
MANIFEST.in
可以用来包含额外文件。pyproject.toml
是一个新的打包标准,它正在逐渐取代setup.py
。
好了,今天的讲座就到这里。希望大家都能掌握 Python 打包的技巧,让你的代码飞得更高!
一些提示和技巧
- 尽量使用
find_packages()
函数自动查找 Python 包,避免手动列出所有包。 - 使用
install_requires
指定依赖包,让pip
自动下载和安装依赖。 - 使用
classifiers
描述你的包的特性,方便用户搜索和发现你的包。 - 编写详细的文档,让用户更容易理解和使用你的包。
- 使用版本控制系统(比如 Git)管理你的代码,方便协作和维护。
- 发布你的包到 PyPI 上,让全世界的人都能使用你的代码。
常见问题
-
Q: 我应该使用
distutils
还是setuptools
?A: 除非你有特殊的需求,否则应该使用
setuptools
。它更强大、更方便。 -
Q: 我的 C 扩展编译失败了,怎么办?
A: 检查你的编译选项、头文件目录、库文件目录等等是否正确。确保你的系统已经安装了所有必要的依赖。
-
Q: 我的包安装后无法运行,怎么办?
A: 检查你的
entry_points
是否正确配置。确保你的脚本或者模块在PATH
环境变量中。 -
Q: 如何发布我的包到 PyPI 上?
A: 首先,你需要注册一个 PyPI 账号。然后,你需要安装
twine
包。最后,你可以使用twine upload
命令上传你的包。
表格:setup()
函数常用参数总结
参数名 | 描述 |
---|---|
name |
包的名字。 |
version |
包的版本号。 |
packages |
一个包含所有 Python 包的列表。 |
py_modules |
一个包含所有 Python 模块的列表(单个 .py 文件)。 |
install_requires |
一个包含所有依赖包的列表。 |
author |
作者的名字。 |
author_email |
作者的邮箱。 |
description |
包的简短描述。 |
long_description |
包的详细描述(通常从 README 文件读取)。 |
url |
包的网站地址。 |
classifiers |
一系列分类器,用于描述你的包的特性(比如编程语言、操作系统、许可证等等)。 |
ext_modules |
一个包含所有 C 扩展的列表。 |
entry_points |
定义命令行工具或者其他入口点。 |
python_requires |
指定包所需要的 Python 版本,例如 '>=3.6' 表示需要 Python 3.6 或更高版本。 |
package_data |
用于指定包中需要包含的非 Python 文件,例如 {'my_package': ['*.txt', '*.dat']} 表示 my_package 包中所有 .txt 和 .dat 文件。 |
exclude_package_data |
用于指定包中需要排除的非 Python 文件,例如 {'my_package': ['*.pyc']} 表示 my_package 包中排除所有 .pyc 文件。 |
data_files |
用于指定需要安装到特定位置的数据文件,例如 [('share/my_package', ['data/config.ini'])] 表示将 data/config.ini 文件安装到 share/my_package 目录下。 |
keywords |
一个包含与包相关的关键词的列表,方便用户搜索。 |
license |
包的许可证类型,例如 'MIT' 或 'GPL-3.0' 。 |
希望这篇文章对你有所帮助! 祝你打包愉快!