4. 包的分发基础:setuptools库介绍、setup.py文件的编写、包的元数据配置、包的构建

好,咱们进入实战环节了。前面几章我们把模块和包的结构讲清楚了,但有个问题——你写好的包怎么给别人用?总不能让人家去GitHub上手动下载文件夹吧?

这就引出了包的分发机制。说白了,就是把你写的代码打包成一个标准格式,扔到PyPI上,别人一个pip install就能装好。我刚开始学的时候觉得这步挺玄乎的,后来发现其实就是写一个setup.py文件的事。

4.1 setuptools库介绍

setuptools是Python官方推荐的打包工具。它替代了老旧的distutils,功能更强大。你想想看,如果没有它,你要手动处理依赖关系、版本号、元数据……那得多痛苦。

我个人习惯用setuptools配合wheel一起用。为什么?因为wheel格式的包安装速度更快,而且不需要执行setup.py里的代码。嗯,这里要注意:wheel不是setuptools自带的,需要单独安装。

小提示: 安装命令很简单:pip install setuptools wheel。我建议你在虚拟环境里操作,避免污染全局Python环境。

4.2 setup.py文件的编写

这是整个分发流程的核心。setup.py是一个Python脚本,它调用setuptools的setup()函数,告诉打包工具你的包长什么样。

来看一个最基础的例子:

from setuptools import setup, find_packages

setup(
    name='myutils',
    version='0.1.0',
    packages=find_packages(),
    install_requires=[
        'requests>=2.25.0',
        'numpy>=1.19.0',
    ],
)

这段代码干了三件事:

  • 告诉setuptools包名叫myutils
  • 自动发现所有子包(find_packages()会扫描当前目录下的所有__init__.py文件)
  • 声明了两个依赖:requests和numpy

我在项目中遇到过一个问题:有人把packages参数写成了手动列表,结果漏掉了一个子包,用户安装后导入时报错。所以,能用find_packages()就别手写。

4.3 包的元数据配置

元数据就是包的「身份证」。它告诉用户这个包是谁写的、干什么用的、版本号是多少。配置好了,用户在PyPI上就能看到完整信息。

常用的元数据字段:

字段名 说明 示例
name 包名,必须唯一 'myutils'
version 版本号,遵循语义化版本 '0.1.0'
author 作者姓名 '张三'
author_email 作者邮箱 'zhangsan@example.com'
description 简短描述 '一个实用的工具包'
long_description 详细描述,通常从README读取 open('README.md').read()
url 项目主页 'https://github.com/...'
classifiers 分类标签 ['Programming Language :: Python :: 3']

一个更完整的例子:

from setuptools import setup, find_packages

with open('README.md', 'r', encoding='utf-8') as f:
    long_description = f.read()

setup(
    name='myutils',
    version='0.1.0',
    author='张三',
    author_email='zhangsan@example.com',
    description='一个实用的工具包',
    long_description=long_description,
    long_description_content_type='text/markdown',
    url='https://github.com/zhangsan/myutils',
    packages=find_packages(),
    classifiers=[
        'Programming Language :: Python :: 3',
        'License :: OSI Approved :: MIT License',
        'Operating System :: OS Independent',
    ],
    python_requires='>=3.6',
    install_requires=[
        'requests>=2.25.0',
    ],
)
避坑指南: 我曾经在long_description_content_type上栽过跟头。如果你用Markdown写的README,一定要加上long_description_content_type='text/markdown',否则PyPI会把它当纯文本渲染,效果惨不忍睹。

4.4 包的构建:sdist和bdist_wheel

写好setup.py后,下一步就是构建。构建会生成一个可分发的文件,你可以上传到PyPI或者直接发给别人。

两种主要的构建方式:

  • sdist(源码分发包):生成.tar.gz文件,包含源码。用户安装时需要编译,速度慢。
  • bdist_wheel(wheel分发包):生成.whl文件,是预编译的。安装时直接解压,速度快。

我个人强烈推荐用wheel格式。为什么?因为用户拿到.whl文件后,安装就是秒级的事。而sdist在某些平台上可能因为缺少编译工具而失败。

构建命令:

# 构建源码包
python setup.py sdist

# 构建wheel包
python setup.py bdist_wheel

# 或者一步到位,两个都构建
python setup.py sdist bdist_wheel

执行后,会在项目目录下生成一个dist/文件夹,里面就是构建好的包。

来看一个构建流程的示意图:

setup.py python setup.py sdist bdist_wheel dist/ myutils-0.1.0.tar.gz myutils-0.1.0-py3-none-any.whl PyPI

构建完成后,你可以用twine工具上传到PyPI。不过那是下一章的内容了,咱们先把构建这一步练熟。

核心要点:
  • setup.py是包分发的入口,必须放在项目根目录
  • 元数据要写全,尤其是name和version,缺一不可
  • 优先使用bdist_wheel构建,用户体验更好
  • 构建产物在dist/目录下,不要手动修改

嗯,这一章的内容就这些。说白了,包分发就是把你的代码「包装」成一个标准产品。写setup.py就像填一份快递单——写清楚寄件人、收件人、物品名称,剩下的交给setuptools去处理。

下一章我们会讲如何把构建好的包上传到PyPI,以及如何管理版本号。到时候你会看到,整个流程其实比想象中简单得多。