好的,各位观众老爷,欢迎来到今天的“Python 包裹大法:从入门到入土,C 扩展也不怕!” 讲座。我是你们的老朋友,包治百病,哦不,包罗万象的 Python 包裹师傅。
今天咱们要聊的是 Python 的 setuptools 和 distutils,这两个家伙可是 Python 包裹界的扛把子,专门负责把你的 Python 代码、C 扩展、数据文件等等,打包成一个方便快捷、人见人爱的包裹,供大家下载安装。
我知道,一开始看到 setuptools 和 distutils,很多人都会一脸懵逼:“这俩啥玩意儿?有啥区别?我该用哪个?” 别慌,听我慢慢道来。
distutils:老牌劲旅,但已显疲态
distutils 其实是 Python 的标准库自带的,相当于 Python 的“亲儿子”。它历史悠久,资格老,但是功能相对简单,很多时候不太够用。就像你家里的老式自行车,能骑,但是爬坡有点费劲,功能也比较单一。
setuptools:功能强大,社区支持广泛
setuptools 则是社区开发的,相当于 Python 的“干儿子”。它功能更强大,提供了很多高级特性,比如:
- 依赖管理: 可以自动下载和安装你的包所依赖的其他包。
- 入口点 (Entry Points): 可以让你定义的函数或类直接通过命令行调用。
- 插件机制: 可以方便地扩展
setuptools的功能。
setuptools 就像一辆性能卓越的山地车,爬坡轻松,功能丰富,还能改装升级。
那么,我该用哪个呢?
答案很简单:永远选择 setuptools!
distutils 已经被 setuptools 取代,它不会再更新,而且 setuptools 已经包含了 distutils 的所有功能,并且提供了更多更强大的特性。就像你不会再用老式自行车去参加山地车比赛一样,别再纠结 distutils 了,拥抱 setuptools 吧!
好了,废话不多说,咱们开始实战!
1. 创建一个简单的 Python 包
首先,咱们创建一个最简单的 Python 包,只有一个 .py 文件。
my_package/
├── my_module.py
└── setup.py
my_module.py 的内容:
def greet(name):
"""
向指定的人打招呼。
"""
return f"Hello, {name}!"
setup.py 的内容:
from setuptools import setup, find_packages
setup(
name='my_package', # 包的名字
version='0.1.0', # 包的版本
description='A simple Python package', # 包的描述
author='Your Name', # 作者
author_email='[email protected]', # 作者邮箱
packages=find_packages(), # 自动查找包下面的所有模块
# install_requires=['requests'], # 依赖的其他包,这里先不加
)
setup.py 文件详解:
name: 包的名字,也就是你以后pip install的时候用的名字。version: 包的版本号,遵循语义化版本规范 (Semantic Versioning)。description: 包的简单描述,方便别人了解你的包是干嘛的。author: 作者的名字。author_email: 作者的邮箱。packages: 这个是关键!find_packages()函数会自动查找当前目录下所有包含__init__.py文件的目录,并将它们作为包包含进来。install_requires: 列出你的包所依赖的其他包,setuptools会自动下载和安装这些依赖。
2. 打包你的代码
打开你的终端,进入 my_package 目录,然后运行以下命令:
python setup.py sdist bdist_wheel
这条命令会生成两个压缩包:
sdist:源码包 (source distribution),包含你的 Python 代码、setup.py文件和其他必要的文件。bdist_wheel:编译后的包 (built distribution),是一种二进制格式的包,安装速度更快。
这两个压缩包都会放在 dist 目录下。
3. 安装你的包
你可以使用 pip 安装你刚刚打包好的包:
pip install dist/my_package-0.1.0-py3-none-any.whl # 安装 wheel 包
# 或者
pip install dist/my_package-0.1.0.tar.gz # 安装源码包
安装完成后,你就可以在你的 Python 代码中使用 my_module 了:
import my_module
print(my_module.greet("World")) # 输出:Hello, World!
4. 添加依赖
假设你的包依赖 requests 库,你需要在 setup.py 文件中添加 install_requires 参数:
from setuptools import setup, find_packages
setup(
name='my_package',
version='0.1.0',
description='A simple Python package',
author='Your Name',
author_email='[email protected]',
packages=find_packages(),
install_requires=['requests'], # 添加 requests 依赖
)
然后重新打包你的代码,安装后,setuptools 会自动安装 requests 库。
5. 添加数据文件
有时候,你的包需要包含一些数据文件,比如配置文件、图像文件等等。你可以使用 package_data 参数来指定这些文件:
from setuptools import setup, find_packages
setup(
name='my_package',
version='0.1.0',
description='A simple Python package',
author='Your Name',
author_email='[email protected]',
packages=find_packages(),
install_requires=['requests'],
package_data={
'my_package': ['data/*.txt'], # 包含 my_package/data 目录下的所有 .txt 文件
},
)
注意: package_data 是一个字典,key 是包名,value 是一个包含文件模式的列表。文件模式可以使用通配符,比如 *.txt 表示所有 .txt 文件。
6. C 扩展:进阶玩法
现在,咱们来点高级的:如何打包包含 C 扩展的 Python 包?
首先,你需要一个 C 语言源文件,比如 my_module.c:
#include <Python.h>
static PyObject* greet(PyObject* self, PyObject* args) {
const char* name;
if (!PyArg_ParseTuple(args, "s", &name)) {
return NULL;
}
char greeting[256];
snprintf(greeting, sizeof(greeting), "Hello from C, %s!", name);
return PyUnicode_FromString(greeting);
}
static PyMethodDef MyModuleMethods[] = {
{"greet", greet, METH_VARARGS, "Greet someone from C."},
{NULL, NULL, 0, NULL} /* Sentinel */
};
static struct PyModuleDef mymodule = {
PyModuleDef_HEAD_INIT,
"my_module", /* name of module */
NULL, /* module documentation, may be NULL */
-1, /* size of per-interpreter state of the module,
or -1 if the module keeps state in global variables. */
MyModuleMethods
};
PyMODINIT_FUNC
PyInit_my_module(void)
{
return PyModule_Create(&mymodule);
}
这个 C 代码定义了一个 greet 函数,它接受一个字符串参数,并返回一个包含问候语的字符串。
然后,你需要修改 setup.py 文件,告诉 setuptools 如何编译这个 C 扩展:
from setuptools import setup, Extension
setup(
name='my_package',
version='0.1.0',
description='A simple Python package with a C extension',
author='Your Name',
author_email='[email protected]',
packages=['my_package'],
ext_modules=[
Extension(
'my_package.my_module', # 扩展模块的名字
['my_package/my_module.c'], # C 语言源文件
),
],
)
ext_modules 参数:
ext_modules是一个列表,包含Extension对象。Extension对象指定了 C 扩展模块的名字和源文件。'my_package.my_module'表示扩展模块的名字,必须与 Python 包的结构对应。['my_package/my_module.c']表示 C 语言源文件的路径。
注意: 你需要安装 C 编译器才能编译 C 扩展。在 Linux 上,你可以使用 gcc;在 Windows 上,你需要安装 Visual Studio。
重新打包你的代码,安装后,你就可以在你的 Python 代码中使用 C 扩展了:
import my_package.my_module
print(my_package.my_module.greet("World")) # 输出:Hello from C, World!
7. Entry Points:让你的代码更易用
entry_points 是 setuptools 的一个非常强大的特性,它可以让你定义的函数或类直接通过命令行调用。
假设你想要创建一个命令行工具,可以用来向指定的人打招呼。你可以在 setup.py 文件中添加 entry_points 参数:
from setuptools import setup, find_packages
setup(
name='my_package',
version='0.1.0',
description='A simple Python package with a command-line interface',
author='Your Name',
author_email='[email protected]',
packages=find_packages(),
entry_points={
'console_scripts': [
'greet = my_module:greet', # greet 命令对应 my_module.py 中的 greet 函数
],
},
)
entry_points 参数:
entry_points是一个字典,key 是入口点的类型,value 是一个包含入口点定义的列表。'console_scripts'表示命令行脚本入口点。'greet = my_module:greet'表示greet命令对应my_module.py中的greet函数。
重新打包你的代码,安装后,你就可以在命令行中使用 greet 命令了:
greet World # 输出:Hello, World!
8. 一些技巧和注意事项
- 使用
.gitignore文件: 在你的包的根目录下创建一个.gitignore文件,排除一些不需要打包的文件,比如.pyc文件、__pycache__目录等等。 - 使用
MANIFEST.in文件: 如果你需要包含一些find_packages()无法自动找到的文件,比如数据文件、文档等等,可以使用MANIFEST.in文件来指定这些文件。 - 使用
requirements.txt文件: 你可以使用pip freeze > requirements.txt命令将你的项目的依赖列表导出到一个requirements.txt文件中,然后在setup.py文件中使用install_requires参数来读取这个文件。 - 发布你的包到 PyPI: 你可以将你的包发布到 PyPI (Python Package Index),让全世界的人都可以下载和使用你的包。你需要注册一个 PyPI 账号,然后使用
twine工具上传你的包。
表格总结:distutils vs setuptools
| 特性 | distutils |
setuptools |
|---|---|---|
| 标准库 | 是 | 否 |
| 依赖管理 | 无 | 有 |
| 入口点 (Entry Points) | 无 | 有 |
| 插件机制 | 无 | 有 |
| 功能 | 简单 | 强大 |
| 社区支持 | 较少 | 广泛 |
| 是否推荐使用 | 否 | 是 |
最后,送给大家一句忠告:
打包虽好,可不要贪杯哦! 合理规划你的包结构,编写清晰的 setup.py 文件,才能让你的代码更好地服务于人民群众!
今天的讲座就到这里,感谢大家的观看! 如果有什么问题,欢迎在评论区留言,我会尽力解答。 下次再见!