• 【新】使用setuptools打包Python项目


    如何使用setuptools打包Python项目

    pypi 官网给出了4个打包 whl 格式的工具:HatchlingsetuptoolsFlitPDM

    初步了解了一下,还是用 setuptools 。

    本文主要内容包括:pyproject.toml 的简单配置、使用 python -m build 打包、使用 cython 编译模块、生成命令等内容。

    本文仅为学习经验,深入学习请参考官方文档。

    一、准备

    安装 setuptools 和 build

    pip install --upgrade setuptools    # 65.5.0
    # 安装 build 以可以运行命令: python -m build
    pip install --upgrade build    # 0.9.0
    
    • 1
    • 2
    • 3

    二、项目结构

    以名为 pypackage 的项目为例(src 结构)

    pypackage/
    ├── LICENSE           # LICENSE
    ├── pyproject.toml    # 项目配置信息
    ├── README.md         # 自述文件
    ├── src               # 源码 
    │   └── pypackage
    │       ├── __init__.py
    │       └── __main.py
    └── tests             # 相关的测试
        └── test_main.py
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    还有另一种结构(flat 结构)

    pypackage/
    ├── LICENSE           # LICENSE
    ├── pyproject.toml    # 项目配置信息
    ├── README.md         # 自述文件
    │── pypackage         # 名字为项目名
    │   ├── __init__.py
    │   └── __main.py
    └── tests             # 相关的测试
        └── test_main.py
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    setuptools 具有自动检测项目代码目录的功能。另外需要说明的是,使用 src-layout 的形式时,会添加 src 下的所有目录,建议 src 下只保留一个与项目名同名的目录

    三、简单打包

    3.1 文件内容

    src/pypackage/__init__.py

    from .__main import say_hello
    
    • 1

    src/pypackage/__main.py

    def say_hello(name: str):
        __private_say(name)
    
    def __private_say(name: str):
        print(f"private say: Hello, {name}!")
    
    • 1
    • 2
    • 3
    • 4
    • 5

    tests/test_main.py

    TEST_RELEASE = True
    if not TEST_RELEASE:
        import sys
        import pathlib
        root_dir = pathlib.Path(__file__).absolute().parent.parent
        sys.path.insert(0, str(root_dir.joinpath("src")))
    
    # using pytest
    def test_hello():
        import pypackage 
        pypackage.say_hello("yluuu")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    pyproject.toml

    [build-system]
    requires = ["setuptools"]
    build-backend = "setuptools.build_meta"
    
    [project]
    name = "pypackage"
    version = "0.0.1"
    dependencies = [
        "requests",
        "rtoml; python_version<'3.8'",
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3.2 打包项目

    默认情况会根据依赖包创建一个隔离的虚拟环境来打包,但我是离线环境,所以不能用这种方式,需要添加 --no-isolation 选项

    python -m build --no-isolation
    
    • 1

    打包如果成功会提示:Successfully built pypackage-0.0.1.tar.gz and pypackage-0.0.1-py3-none-any.whl,共生成了两个文件

    3.3 安装测试

    使用 pip 安装 whl 文件,查看安装目录 ~/miniconda3/lib/python3.7/site-packages/pypackage

    -rw-rw-r--  1 yl yl   30 1031 08:19 __init__.py
    -rw-rw-r--  1 yl yl  124 1031 08:19 __main.py
    drwxrwxr-x  2 yl yl 4.0K 1031 08:19 __pycache__
    
    • 1
    • 2
    • 3

    在项目根目录下直接运行 pytest 命令,测试没有问题

     ~/test/pypackage ----------------- base py | yl@jisuan01 | 08:30:28 
    > pytest
    ======================== test session starts =========================
    platform linux -- Python 3.7.13, pytest-7.2.0, pluggy-1.0.0
    rootdir: /home/yl/test/pypackage
    collected 1 item                                                     
    
    tests/test_main.py .                                           [100%]
    
    ========================= 1 passed in 0.01s ==========================
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    四、自定义打包

    4.1 添加子模块

    setuptools 有自动发现的功能:对于常用的两种项目形式(src-layout、flat-layout),setuptools 可以自动扫描并发现代码目录。

    添加文件 src/pypackage/lib1/mod1.py

    def func1():
        return 1
    
    • 1
    • 2

    添加文件 src/pypackage/lib2/mod2.py

    def func2():
        return 2
    
    • 1
    • 2

    src 及 项目下的模块均可以自动被添加

    adding 'pypackage/__init__.py'
    adding 'pypackage/__main.py'
    adding 'pypackage/lib1/mod1.py'    # 自动添加了
    adding 'pypackage/lib2/mod2.py'    # 自动添加了
    adding 'pypackage-0.0.1.dist-info/LICENSE'
    adding 'pypackage-0.0.1.dist-info/METADATA'
    adding 'pypackage-0.0.1.dist-info/WHEEL'
    adding 'pypackage-0.0.1.dist-info/top_level.txt'
    adding 'pypackage-0.0.1.dist-info/RECORD'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    更多请参考: https://setuptools.pypa.io/en/latest/userguide/package_discovery.html

    4.2 只生成 whl

    添加选项 --wheel 即可

    python -m build --no-isolation --wheel
    
    • 1

    4.3 包含/排除数据

    如 添加 keep.parquet 排除 exclude.txt

    pypackage/
    ├── ...
    │── src
    │   └── pypackage
    │       ├── __init__.py
    │       ├── __main.py
    │       └── data 
    │           ├── keep.parquet
    │           └── exclude.txt
    └── ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    pyproject.toml 添加

    [tool.setuptools]
    include-package-data = true
    
    [tool.setuptools.package-data]
    pypackage = ["data/*.parquet"]
    
    [tool.setuptools.exclude-package-data]
    pypackage = ["data/*.txt"]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    其实 include-package-data 默认就是 true 的

    打包记录

    adding 'pypackage/__init__.py'
    adding 'pypackage/__main.py'
    adding 'pypackage/data/keep.parquet'    # 这里
    adding 'pypackage/lib1/mod1.py'
    adding 'pypackage/lib2/mod2.py'
    adding 'pypackage-0.0.1.dist-info/LICENSE'
    adding 'pypackage-0.0.1.dist-info/METADATA'
    adding 'pypackage-0.0.1.dist-info/WHEEL'
    adding 'pypackage-0.0.1.dist-info/top_level.txt'
    adding 'pypackage-0.0.1.dist-info/RECORD'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    更多请参考: https://setuptools.pypa.io/en/latest/userguide/datafiles.html

    4.4 编译模块

    使用 cython

    pip install cython --upgrade
    
    • 1

    创建 setup.py

    # 方式一:按 setuptools 文档,使用 Extension 类,使用 cython 编译时无法设置 language_level TODO 待解决
    # from setuptools import setup, Extension
    
    # setup(
    #     ext_modules=[
    #         Extension(
    #             name="pypackage.lib1.mod1",
    #             sources=["src/pypackage/lib1/mod1.pyx"],
    #         )
    #     ]
    # )
    
    # 方式二:直接调用 cython,可以设置 language_level
    # 备注: 子模块尽量添加 __init__.py 以确保编译后的库文件可以正常打包到子模块中。
    from setuptools import setup
    from Cython.Build import cythonize
    
    setup(
        ext_modules=cythonize(module_list=[
            "src/pypackage/lib1/mod1.pyx"
        ], language_level=3)
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    另外在 pyproject.toml 中排除 c 文件打包

    [tool.setuptools.exclude-package-data]
    pypackage = ["info/*.txt"]
    "pypackage.lib1" = ["*.c"]
    
    • 1
    • 2
    • 3

    src/pypackage/lib1/mod1.py 重命名为 src/pypackage/lib1/mod1.pyx,并在 src/pypackage/lib1 下添加 __init__.py 文件

    然后使用 python -m build 时会自动调用 setup.py 生成库文件并打包

    单独编译模块时运行 python setup.py build_ext

    备注:测试过程中的坑,都在上面的注释里了

    adding 'pypackage/__init__.py'
    adding 'pypackage/__main.py'
    adding 'pypackage/info/keep.parquet'
    adding 'pypackage/lib1/__init__.py'
    adding 'pypackage/lib1/mod1.cpython-37m-x86_64-linux-gnu.so'
    adding 'pypackage/lib2/mod2.py'
    adding 'pypackage-0.0.1.dist-info/LICENSE'
    adding 'pypackage-0.0.1.dist-info/METADATA'
    adding 'pypackage-0.0.1.dist-info/WHEEL'
    adding 'pypackage-0.0.1.dist-info/top_level.txt'
    adding 'pypackage-0.0.1.dist-info/RECORD'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    4.5 生成可执行命令

    __main.py 中添加以下内容作为可执行命令的入口

    def hello():
        __private_say("pypackage")
    
    • 1
    • 2

    然后在 pypackage.toml 中添加名为 pypackage-hello 的命令(在命令行中可直接运行 pypackage-hello

    [project.scripts]
    pypackage-hello = "pypackage.__main:hello"
    
    • 1
    • 2

    完成。重新打包安装后效果如下:

    > pypackage-hello                                             
    private say: Hello, pypackage!
    
    • 1
    • 2
  • 相关阅读:
    2023年山东省职业院校技能竞赛“网络安全”项目竞赛样题
    nuxt添加aos动效
    《500强高管谈VE》-面向STAKEHOLDERS东方企业的VM
    c++访问修饰符与继承关系
    EasyRecovery2023互盾免费数据恢复软件下载功能介绍
    阿里云2023年双十一优惠活动整理汇总
    js 找出两个数组中的重复元素
    UVa 762 - We Ship Cheap
    兄弟MFC-7480D打印机墨粉清零方法(图解)
    Weblogic SSRF 漏洞(CVE-2014-4210)分析
  • 原文地址:https://blog.csdn.net/ylhlly/article/details/127636162