红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 编程语言
  3. Python
  4. 正文

[转载]Python打包:setuptools与setup.py/.cfg入门简介

2021年4月15日 1195点热度 0人点赞 0条评论

[转载]Python打包:setuptools与setup.py/.cfg入门简介

谭九鼎

谭九鼎

一只普通的计算机爱好者

原文地址:https://zhuanlan.zhihu.com/p/261579357

setuptools是python自带的用来构建包的工具,构建出来的wheel(.whl)可供其他人pip install和import。 知乎上已经有了一个1500多赞的文章介绍它,但是内容不太符合我的口味。

本文以一个最简单的hello world包为例子,一步步操作,作为构建Python包的入门指引。首先创建一个hello.py:

def say(to):
    print('hello', to)

此时在另一个同级的test.py中,已经可以使用该函数了:

import hello
hello.say('world') # hello world

但在别的地方运行test.py会报ModuleNotFoundError,这是当然的,本文就是为了解决此问题。但此问题先放一放,先来添加另一个特性。

如果直接用python命令行运行此文件:python hello.py或python -m hello,它什么也不会输出,因为它只定义了函数,而没有调用。 一般来说对于库函数,无输出是正常的,它们本来就没设计成能被直接调用。不过这里我们选择给它添加这个功能,把hello.py改成以下内容:

import sys
​
def say(to):
    print('hello', to)
​
if __name__ == '__main__':
    say(sys.argv[1])

原理是python命令行运行目标时会把__name__设为字符串__main__,就能进入if中;而import时不会,就不会产生不想要的输出。

调用:python hello.py world或python -m hello world,能输出hello world。

接下来就是创建包的关键,创建一个setup.py:

import setuptools
setuptools.setup(
    name='hellopkg', # 包的名字,可随意取
    py_modules=['hello'] # 对应hello.py,也是安装了包之后实际import的名字
)

运行命令,注意那个点不能省,它代表当前目录:

pip install .

完成,你已经可以在任何地方import hello; hello.say('world')了,也可以命令行调用python -m hello world。pip list可以看到包的名字是hellopkg且不存在名叫hello的包,版本号没指定默认是0.0.0,python -m hellopkg world会失败,这说明包的名字与安装好了包后能获得的模块名是无关的。

接下来我们实现在命令行中直接用hello world输出hello world,为此需要单独把入口函数拿出来,修改hello.py:

import sys
​
def say(to):
    print('hello', to)
​
def main():
    say(sys.argv[1])
​
if __name__ == '__main__':
    main()

再在setup.py中添加一行:

import setuptools
setuptools.setup(
    name='hellopkg',
    py_modules=['hello'],
    entry_points={'console_scripts': ['pyhello = hello:main']}
    # 定义终端入口点,将产生pyhello.exe,会执行hello模块的main函数
)

再pip install .安装一遍。完成,你已经可以在命令行中用pyhello world输出hello world了,把pyhello改成hello再装一遍就是hello world输出hello world。

我们还希望把源代码打包成一个单独的whl文件,但是wheel这个包不自带。创建一个虚拟环境模拟最初的情况:

pip uninstall hellopkg # 卸载系统级别的包
python -m venv .venv
.venv\Scripts\activate
pip install -U pip setuptools wheel

然后使用pip wheel .就能在当前目录下生成hellopkg-0.0.0-py3-none-any.whl,这就是打包好的包,用pip install hellopkg-0.0.0-py3-none-any.whl就能装上,也可以发给别人或者上传到PyPI。

传统方法是用python setup.py bdist_wheel,会生成三个文件夹:build和xxx.egg-info文件夹是构建时的临时文件及元数据,方便你检查哪些文件被怎样处理了;dist文件夹中有whl文件,与上条命令一致,其实上调命令就是调用了本条命令。bdist_wheel是一条必须装了wheel包才能使用的verb,用--help-commands可查看所有verb,其中sdist生成源代码压缩包hellopkg-0.0.0.tar.gz,也能被pip install安装。

以上就是setuptools和setup.py的最简陋的用法,下面介绍简单但且正常的用法。正常情况下源代码应该这样组织:

.
├── hello
│   ├── hello_impl.py
│   ├── __init__.py
│   └── __main__.py
├── LICENSE
├── Readme.md
├── setup.cfg
└── setup.py

各个文件的内容:

# setup.py;会自动读取setup.cfg中的设置
import setuptools
setuptools.setup() # 也可有参调用,则会覆盖.cfg的对应条目
​
# setup.cfg;所有条目见 https://setuptools.readthedocs.io/en/latest/userguide/declarative_config.html
[metadata]
name = hellokpg
version = 1.0
author = xxx
long_description = file: Readme.md # 从文件中读取
license = MIT
url = https://github.com/user/repo
classifiers = # PyPI的分类,类似于标签,所有条目见 https://pypi.org/pypi?%3Aaction=list_classifiers
    Development Status :: 3 - Alpha
    Programming Language :: Python :: 3
​
[options]
packages = find: # 自动搜索存在__init__.py的文件夹作为包
install_requires = # 依赖,pip安装时靠的就是这个而不是requirements.txt
    requests
​
[options.entry_points]
console_scripts =
    pyhello = hello.__main__:main
​
# hello_impl.py
def say(to):
    print('hello', to)
​
# __init__.py
from .hello_impl import say
​
# __main__.py
from . import say
import sys
def main():
    say(sys.argv[1])
if __name__ == '__main__':
    main()

使用setup.cfg而不是setup.py的理由是,前者是声明式的配置文件,后者是实际的python代码,可能不安全。setuptools的文档中推荐从setup.py迁移到setup.cfg。 其实未来应该会使用pyproject.toml,但是现在setuptools不支持将它代替setup.cfg,只能代替setup.py,所以就没什么用,有个issue讨论但是没有ETA。

上传到PyPI我就不写了,用的是twine,看其它人的教程吧。我暂时没有该需求,因为pip支持install git+https://github.com/...进行安装,只要那个repo中存在setup.py就行,实际上是下下来在本机构建。wheel的好处是不用在本机上编译C扩展,我这样做没有用到wheel的优势,但是我不会C扩展所以也无所谓。

一些提示:

  • 推荐用setup.cfg,除非想结构最精简;未来用pyproject.toml

  • .egg已经deprecated了,用.whl;dependency_links也弃用了

  • 不需要用distutils,有讨论将它从标准库中移除

  • 打错字很可能没有任何提示,比如entry_points写成entry_point打包时不生效也没有任何报错

  • whl概念上的“包”和python概念上的“包”是两个概念,前者更类似于“软件包”,后者是存在__init__.py的文件夹;本文中“包的名字,可随意取”等句说的就是前者

  • 有一些工具能从setup的依赖中生成requirements.txt,但是对于不锁定依赖的简单项目就无所谓了

  • pip install -e在开发时很有用

  • setup.py install用的是easy_install,是pip的前身,没必要用

  • 最好在一开始就建立好虚拟环境,方便测试。本文为了降低最初的学习曲线没在一开始启用

  • 我看不出有什么理由用pbr

进一步学习的资源:

这些并不是本文的参考资料,而是我推荐阅读的文章。因为在学setuptools的过程中我对搜出来的垃圾文章深感痛心,setup.cfg可是在python2的时期就存在了,居然少有文章提到。

  • https://lingxiankong.github.io/2013-12-23-python-setup.html

  • https://www.cnblogs.com/cposture/p/9029023.html

  • setuptools官方文档:https://setuptools.readthedocs.io/en/latest/userguide/index.html

  • 绝对引用和相对引用:https://www.cnblogs.com/dream08/p/12878331.html

  • 处理命令行参数:https://github.com/HelloGitHub-Team/Article/tree/master/contents/Python/cmdline

  • 关于wheel的一些特性:https://realpython.com/python-wheels/

  • PyPI打包文档:https://packaging.python.org/ov

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: Python 打包 模块
最后更新:2021年4月15日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇
下一篇 >

文章评论

取消回复

*

code

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号