好的,各位观众老爷,欢迎来到今天的“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
文件,才能让你的代码更好地服务于人民群众!
今天的讲座就到这里,感谢大家的观看! 如果有什么问题,欢迎在评论区留言,我会尽力解答。 下次再见!