Python打包实战指南:全面对比setup.py与pyproject.toml,深度解析其优劣与应用场景

在 Python 开发中,创建可安装的包是分享代码的重要方式。本文将深入解析两种主流打包方法——setup.pypyproject.toml,并通过一个实际项目示例,展示如何使用现代的 pyproject.toml 方法构建、测试和发布 Python 包。

一、setup.py 与 pyproject.toml 的区别

1. setup.py(传统方式)

setup.py 是 Python 打包的传统方法,使用 setuptoolsdistutils 定义包的元数据和依赖关系。典型示例如下:

from setuptools import setup

setup(
    name='mypackage',
    version='0.1',
    packages=['mypackage'],
    install_requires=['requests']
)

使用方法

python setup.py sdist bdist_wheel
pip install .

2. pyproject.toml(现代方式)

自 PEP 518 引入后,pyproject.toml 成为推荐的配置方式。它分离了构建系统配置和包元数据,支持多种构建工具(如 setuptoolspoetry 等)。示例:

[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "mypackage"
version = "0.1"
dependencies = ["requests"]

使用方法

pip install .

二、为什么推荐 pyproject.toml?

  1. 标准化与兼容性:符合最新打包标准,与各种工具兼容性更好。
  2. 简化配置:分离构建系统和元数据,使配置更清晰。
  3. 多构建系统支持:支持多种工具,提供更大灵活性。
  4. 安全性:减少对自定义脚本的依赖,降低风险。

实际场景中的必要性

假设你正在开发一个复杂的机器学习库,涉及多个依赖项和复杂的构建步骤。使用 pyproject.toml 可以轻松定义这些需求,并确保在不同的开发和部署环境中保持一致性。此外,许多现代工具(如 CI/CD 系统)已经内置了对 pyproject.toml 的支持,简化了自动化流程。

构建 Python 包的最佳实践

  1. 新项目使用 pyproject.toml:对于新项目,推荐使用 pyproject.toml,以符合现代打包标准并提高兼容性。
  2. 旧项目逐步迁移:如果维护的是已有使用 setup.py 的项目,可以继续使用,但建议在可行时迁移到 pyproject.toml
  3. 结合使用:在某些情况下,可以同时使用 pyproject.tomlsetup.py,例如用 pyproject.toml 处理大部分配置,而保留一个最小化的 setup.py 来处理特定功能(如构建 C 扩展)。
  4. 使用 setup.cfg:如果希望采用更声明式的格式但仍使用 setup.py,可以考虑使用 setup.cfg,将元数据放在配置文件中,逻辑保留在 setup.py 中。
  5. 利用构建工具:使用如 PoetryFlit 等工具,可以简化依赖管理和打包流程,自动管理 pyproject.toml 和其他相关文件的创建。

三、实战示例:构建和发布一个机器学习包

下面通过一个实际的机器学习项目示例,展示如何使用 pyproject.toml 构建、测试和发布一个 Python 包。

项目概述

我们将构建一个名为 mlpredictor 的包,该包:

  • 包含一个使用 scikit-learn 的简单分类器模型。
  • 提供训练模型和进行预测的功能。
  • 结构化以便发布到 PyPI 和 GitHub。
  • 步骤详解

    1. 创建项目结构
    mlpredictor/
    │
    ├── mlpredictor/
    │   ├── __init__.py
    │   ├── model.py
    │
    ├── tests/
    │   ├── test_model.py
    │
    ├── LICENSE
    ├── README.md
    ├── pyproject.toml
    └── .gitignore
    
    2. 编写代码

    mlpredictor/model.py

    from sklearn.datasets import load_iris
    from sklearn.model_selection import train_test_split
    from sklearn.ensemble import RandomForestClassifier
    import pickle
    
    
    class MLPredictor:
        def __init__(self):
            self.model = None
    
        def train(self):
            iris = load_iris()
            X_train, X_test, y_train, y_test = train_test_split(
                iris.data, iris.target, test_size=0.2, random_state=42
            )
            self.model = RandomForestClassifier()
            self.model.fit(X_train, y_train)
    
        def predict(self, data):
            if not self.model:
                raise Exception("Model is not trained yet!")
            return self.model.predict([data])
    
        def save_model(self, path="model.pkl"):
            with open(path, "wb") as f:
                pickle.dump(self.model, f)
    
        def load_model(self, path="model.pkl"):
            with open(path, "rb") as f:
                self.model = pickle.load(f)
    

    mlpredictor/*init*.py

    from .model import MLPredictor
    
    __all__ = ["MLPredictor"]
    
    3. 创建 pyproject.toml 文件

    pyproject.toml

    [build-system]
    requires = ["setuptools>=42", "wheel"]
    build-backend = "setuptools.build_meta"
    
    [project]
    name = "mlpredictor"
    version = "0.1.0"
    description = "A simple machine learning package using scikit-learn"
    authors = [
        {name = "Ebrahim", email = "ebimsv0501@gmail.com"}
    ]
    license = {text = "MIT"}
    readme = "README.md"
    requires-python = ">=3.6"
    dependencies = [
        "scikit-learn>=1.0",
    ]
    
    [project.urls]
    "Homepage" = "https://github.com/xxx_your_account/mlpredictor"
    
  • [build-system]:指定构建系统要求,这里使用 setuptoolswheel
  • [project]:包含包的元数据,如名称、版本、描述、作者、许可证、依赖项等。
  • 4. 编写测试

    使用 pytest 添加测试。

    tests/test_model.py

    import pytest
    from mlpredictor import MLPredictor
    
    def test_train_and_predict():
        model = MLPredictor()
        model.train()
        result = model.predict([5.1, 3.5, 1.4, 0.2])
        assert len(result) == 1
    
    if __name__ == "__main__":
        pytest.main()
    
    5. 添加 README、License 和 .gitignore

    README.md

    # MLPredictor
    
    MLPredictor 是一个简单的机器学习包,使用 scikit-learn 训练 RandomForest 模型,并使用户能够进行预测。该包旨在演示如何打包 Python 机器学习项目以供分发。
    
    ## 特性
    
    - 在 Iris 数据集上训练 RandomForestClassifier。
    - 训练后对新数据进行预测。
    - 保存和加载训练好的模型。
    
    ## 安装
    
    您可以通过 **PyPI** 或从 **源代码** 安装该包。
    
    ### 通过 PyPI 安装
    
    ```bash
    pip install mlpredictor
    

    通过源代码安装(GitHub)

    git clone https://github.com/xxx_your_account/mlpredictor.git
    cd mlpredictor
    pip install .
    

    使用方法

    安装后,可以使用 MLPredictor 训练模型并进行预测。

    示例:训练和预测

    from mlpredictor import MLPredictor
    
    # 初始化预测器
    predictor = MLPredictor()
    
    # 在 Iris 数据集上训练模型
    predictor.train()
    
    # 对样本输入进行预测
    sample_input = [5.1, 3.5, 1.4, 0.2]
    prediction = predictor.predict(sample_input)
    
    print(f"预测类别: {prediction}")
    

    LICENSE

    可以选择合适的开源许可证,如 MIT License。

    .gitignore

    *.pyc
    __pycache__/
    *.pkl
    dist/
    build/
    
    6. 本地测试包

    通过以下命令安装包:

    pip install .
    

    安装后,运行测试以确保一切正常:

    pytest tests
    

    注意

  • 如果使用 setup.py,它会读取 setup.py 文件以收集包元数据和安装信息,并解析和安装指定的依赖项。

  • 如果使用pyproject.toml ,它会读取该文件,可能指定构建系统要求和配置。执行上述命令后,通常会创建以下目录:

  • Distribution Directory:可能是 build/dist/.eggs/ 目录,具体取决于安装过程以及是源码安装还是 wheel 安装。
  • build/:在构建过程中创建,包含用于创建包的临时文件。
  • dist/:包含从包生成的构建分发文件(如 wheel 文件)。
  • egg-info/.egg-info/:包含有关已安装包的元数据,包括其依赖项和版本号。
  • 确保项目正常工作后,继续后续步骤。

    7. 推送到 GitHub
    1. 初始化 Git 仓库

      git init
      git add .
      git commit -m "Initial commit"
      
    2. 创建 GitHub 仓库

      前往 GitHub 并创建一个名为 mlpredictor 的新仓库。

    3. 推送代码到 GitHub

      git remote add origin https://github.com/xxx_your_account/mlpredictor.git
      git branch -M main
      git push -u origin main
      

      注意:将 xxx_your_account 替换为您的 GitHub 用户名。

    8. 发布到 PyPI

    现在项目已经设置并推送到 GitHub,可以将其发布到 PyPI。

    1. 安装必要的工具

      pip install twine build
      
    2. 构建包

      python -m build
      

      这将在 dist/ 目录下创建 .tar.gz.whl 文件。检查 dist/ 目录,确保包含类似以下文件:

      mlpredictor-0.1.0-py3-none-any.whl
      mlpredictor-0.1.0.tar.gz
      
    3. 上传到 PyPI

      twine upload dist/*
      

      您需要一个 PyPI 账户才能上传包。上传成功后,其他人可以通过以下命令安装您的包:

      pip install mlpredictor
      
    9. 安装并使用包

    通过 pip 安装后,可以在 Python 代码中使用该包:

    from mlpredictor import MLPredictor
    
    predictor = MLPredictor()
    predictor.train()
    prediction = predictor.predict([5.1, 3.5, 1.4, 0.2])
    print("Predicted class:", prediction.item())
    
    # 输出示例:
    # Predicted class: 0
    

    五、总结

    在 Python 打包领域,setup.pypyproject.toml 各有其重要性和适用场景。尽管 setup.py 在传统项目中仍然发挥作用,但向 pyproject.toml 的转变代表了 Python 社区向更安全、标准化实践发展的趋势。对于新项目,强烈建议采用 pyproject.toml,因为它不仅简化了打包过程,还提高了与各种工具和库的兼容性。

    通过本文的实战示例,您应该能够掌握如何使用 pyproject.toml 构建、测试和发布一个功能完善的 Python 包。无论是个人项目还是团队协作,遵循这些最佳实践将大大提升项目的可维护性和可扩展性。

    作者:梦想画家

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python打包实战指南:全面对比setup.py与pyproject.toml,深度解析其优劣与应用场景

    发表回复