你是否也想写一个python库,想向社会开源或者企业内部使用,想让别人通过简单的命令pip install 安装你的库。那么setuptools绝对是最好用的python打包与分发工具。

setuptools库的前身是distutils(一个python标准库),setuptools本身不是标准库,所以需要自行安装。setuptools提供的主要的功能有:

  • python库的打包分发
  • 依赖包安装与版本管理
  • python环境限制
  • 生成脚本
  • c/c++ 拓展

首先python库的打包分发方式有两种:源码包source dist(简称sdist)、二进制包binary dist(简称bdist)。

1.源码包sdist

源码包sdist就是我们熟悉的 .zip 、.tar.gz 等后缀文件。就是一个压缩包,里面包含了所需要的的所有源码文件以及一些静态文件(txt文本、css、图片等)。

1.1打包分发源码包命令

$ python setup.py sdist --formats=gztar

setup.py后面会介绍,总之setup.py指定了打包分发的配置信息。--formats 参数用来指定压缩格式,若不指定format格式,那么 sdist 将根据当前平台创建默认格式。在类 Unix 平台上,将创建后缀名为.tar.gz分发包,而在Windows上为 .zip 文件。

执行完该命令,我们可以看到文件夹下多了dist文件夹(包含压缩源码的分发包)和egg-info文件夹(中间临时配置信息)。

1.2安装源码包命令

安装源码包有两种方法,先解压缩源码包,或者直接安装源码包。

  • 先解压缩源码包,再执行setup.py
#
$ python setup.py install
等价于
$ python setup.py build
$ python setup.py install
'''
python setup.py install包括两步:python setup.py build python setup.py install。
这两步可分开执行, 也可只执行python setup.py install, 因为python setup.py install总是会先build后install.
'''
  • 直接pip安装源码包
$ pip install  xxx.zip 

如果是开发阶段,可以用下面两个命令,该命令不会真正的安装包,而是在系统环境中创建一个软链接指向包实际所在目录。 这样在修改包之后不用再安装就能生效,便于调试

$ pip install -e .
等价于
$ python setup.py develop

2.二进制包bdist

python目前主流的二进制包格式是wheel(.whl后缀),它的前身是egg。wheel本质也还是一个压缩包,可以像像zip一样解压缩。与源码包相比,二进制包的特点是不用再编译,也就是安装更快!在使用wheel之前,需要先安装wheel:$ pip install wheel 。

2.1打包分发二进制包命令

和 sdist 一样,可以通过 formats 参数指定包格式。如:

$ python setup.py bdist --formats=rpm
等价于
$ python setup.py build_rpm

此外setuptools还提供了其他命令,起到和formats参数一样的效果,如下:

命令 format参数 note
bdist_dumb tar,gztar,zip…… windows默认zip,Unix默认gztar
build_rpm rpm,srpm
build_wininst wininst
build_wheel wheel 目前主流的二进制包,需要先安装wheel

2.2安装二进制包命令

直接pip就可以了

$ pip install xxx.whl

上面我们讲述了python打包分发的两种方法,很容易发现整个打包过程最重要的就是setup.py,它指定了重要的配置信息。setup.py的内容如下:

from setuptools import setupsetup(name = 'myapp',…………
)

可见最关键的就是setuptools.setup这个函数,由它来控制打包分发,包含以下信息:

  • python库的基本信息(作者、联系方式、当前库的版本等)
  • 需要打包的文件
  • 依赖包安装与版本管理
  • python环境限制
  • 生成脚本
  • c/c++ 拓展
  • cmdclass自定义命令行为

3.python库的基本信息

from setuptools import setupdef readme():with open('README.md', encoding='utf-8') as f:content = f.read()return contentsetup(name = 'myapp', # 包名称version = '1.0', # 版本author = 'lihua', # 作者author_email = 'lihua@163.com', # 作者邮箱description='a example for pack python', # 描述long_description=readme(), # 长文描述long_description_content_type='text/markdown', # 长文描述的文本格式keywords='pack', # 关键词url='https://github.com/lihua/myapp', # 项目主页classifiers=[ # 包的分类信息,见https://pypi.org/pypi?%3Aaction=list_classifiers'Development Status :: 5 - Production/Stable','License :: OSI Approved :: Apache Software License','Operating System :: OS Independent','Programming Language :: Python :: 3','Programming Language :: Python :: 3.6','Programming Language :: Python :: 3.7','Programming Language :: Python :: 3.8','Programming Language :: Python :: 3.9',],license='Apache License 2.0', # 许可证
)

4.需要打包的文件

通过setup函数的这些参数packagesinclude_package_data(其实就是MANIFEST.in文件)、exclude_package_datapackage_datadata_files来指定需要打包的文件。

包含的文件如下:

  • py_modulespackages 参数中所有 Python 源文件
  • ext_modulesorlibraries 参数中提到的所有 C 源文件
  • scripts 参数指定的脚本
  • package_datadata_files 参数指定的所有文件
  • setup.cfgsetup.py
  • 类似于readme的文件(如README、README.txt、 README.rst、README.md)
  • MANIFEST.in 中指定的所有文件

4.1packages参数

packages参数就是用来指示打包分发时需要包含的package,type为list[str]

举个例子:

└── D:\workplace\python\pack_test├──setup.py├──debug│   ├──debug.py├──src│   ├──__init__.py│   ├──pack1│   ├──__init__.py│   ├──main.py│   ├──config.txt│   ├──data│   │   ├──main.py│   │   ├──a.dat

其中setup.py文件内容为

from setuptools import setup
setup(packages=['src'])

运行$ python setup.py sdist --formats=zip打包命令后,发现只打包了src和旗下的__init__.py,pack1不见踪影。

├──src│   ├──__init__.py

说明packages参数时,不会递归的打包子package!只打包当前package!

  • 改为setup(packages=['src','src.pack1','src.pack1.data'])这样就可以了。

但每次都这样写也太费时费力了。所以setuptools提供了两个函数find_namespace_packages,find_packages来快速找到所有的package。

首先明白一点,python中的packages有两种,一种是包含__init__.py的文件夹(姑且叫做普通package),一种是不含__init__.py的文件夹(这是python3引入的Namespace Packages命名空间包)。

  • 改为setup(packages=find_packages())发现没有打包data和debug文件夹!

原来是因为find_packages只会打包内含__init__.py的package,而data和debug文件夹正好没有__init__.py。

  • 改为setup(packages=find_namespace_packages()) 就可以打包data和debug文件夹。

此外如果不想打包debug文件夹,可以给find_namespace_packages传递参数以指定在哪个文件夹下进行搜索,比如setup(packages=find_namespace_packages('src')),这样就不会打包debug文件夹。

上面这些例子中都没有包含非源码文件(如.dat和.txt文件),需要通过别的参数include_package_data(其实就是http://MANIFEST.in文件)、exclude_package_data、package_data来打包非源码文件。

4.2include_package_data(http://MANIFEST.in)

include_package_data是bool类型,默认值为True。当为True时,将根据http://MANIFEST.in文件来打包分发库

http://MANIFEST.in文件指定了一些语法规则,主要是用来打包非源码文件的,语法规则如下:

命令 描述
include pat1 pat2 ... 添加与任何列出的模式匹配的所有文件(文件必须作为相对于项目根目录的路径给出)
exclude pat1 pat2 ... 删除与任何列出的模式匹配的所有文件(文件必须作为相对于项目根目录的路径给出)
recursive-include dir-pattern pat1 pat2 ... 递归dir-pattern及其子文件夹,添加与任何列出的模式匹配的目录下的所有文件
recursive-exclude dir-pattern pat1 pat2 ... 递归dir-pattern及其子文件夹,删除与任何列出的模式匹配的目录下的所有文件
global-include pat1 pat2 ... 在源树中的任何位置添加与任何列出的模式匹配的所有文件
global-exclude pat1 pat2 ... 删除源树中与任何列出的模式匹配的所有文件
graft dir-pattern 添加匹配目录下的所有文件 dir-pattern
prune dir-pattern 删除匹配目录下的所有文件 dir-pattern

还是以之前的例子为例,加入http://MANIFEST.in文件

└── D:\workplace\python\pack_test├──setup.py├──MANIFEST.in├──debug│   ├──debug.py├──src│   ├──__init__.py│   ├──pack1│   ├──__init__.py│   ├──main.py│   ├──config.txt│   ├──data│   │   ├──main.py│   │   ├──a.dat

http://MANIFEST.in文件内容为:

recursive-include . *.txt *.dat # 递归遍历当前文件夹,找到符合*.dat、*.txt的文件
或者
include src/pack1/*.txt
include src/pack1/data/*.dat

4.3package_data

除了通过http://MANIFEST.in的方法来指定,还可以通过package_data参数来指定,这边建议还是统一用http://MANIFEST.in文件的方式,免得造成不一致性。

# setup.py
from setuptools import setup
setup(package_data={'':['*.txt'],'src.pk1':['*.dat']} # 其中''表示所有文件夹下

4.4exclude_package_data

顾名思义就是去除文件

exclude_package_data={'src.pk1':['*.txt']}

5.依赖包安装与版本管理

一个项目库可能会依赖于很多其他库,比如我们安装pandas,该库依赖于numpy。那我们用pip conda这些命令安装时,从来不用操心哪些依赖包需要安装,它们的版本限制是怎么样的,而这些信息是setuptools打包分发库时就确定的。所以当setuptools打包分发库时,要指定依赖包有哪些?它们又有什么限制?

针对依赖包安装与版本管理这项功能,setup函数提供了一些参数install_requires、 setup_requirestests_require 、extras_require 。

from setuptools import setup, find_packagessetup(...# 表明当前模块依赖哪些包,若环境中没有,则会从pypi中自动下载安装!!!install_requires=['docutils>=0.3'],# setup.py 本身要依赖的包,这通常是为一些setuptools的插件准备的配置,这里列出的包,不会自动安装。setup_requires=['pbr'],# 仅在测试时需要使用的依赖,在正常发布的代码中是没有用的。# 在执行python setup.py test时,可以自动安装这三个库,确保测试的正常运行。tests_require=['pytest>=3.3.1','pytest-cov>=2.5.1',],# install_requires 在安装模块时会自动安装依赖包# 而 extras_require 不会,这里仅表示该模块会依赖这些包# 但是这些包通常不会使用到,只有当你深度使用模块时,才会用到,这里需要你手动安装extras_require={'PDF':  ["ReportLab>=1.2", "RXP"],'reST': ["docutils>=0.3"],}
)

关于 install_requires, 有以下四种常用的表示方法:

  1. 'argparse',只包含包名。 这种形式只检查包的存在性,不检查版本。 方便,但不利于控制风险。
  2. 'setuptools==38.2.4',指定版本。 这种形式把风险降到了最低,确保了开发、测试与部署的版本一致,不会出现意外。 缺点是不利于更新,每次更新都需要改动代码。
  3. 'docutils >= 0.3',这是比较常用的形式。 当对某个库比较信任时,这种形式可以自动保持版本为最新。
  4. 'Django >= 1.11, != 1.11.1, <= 2',这是比较复杂的形式。 如这个例子,保证了Django的大版本在1.11和2之间,也即1.11.x;并且,排除了已知有问题的版本1.11.1(仅举例)。 对于一些大型、复杂的库,这种形式是最合适的。

6.python环境限制

有时我们也需要对python的版本进行限制,可以通过参数python_requires 来实现。

setup(python_requires='>=2.7, <=3')

7.生成脚本

有时候我们的库包含了一些非常重要的功能,每次都提供python XXX.py来运行不太方便,最好是把脚本放入系统环境path,以命令行的形式来执行。比如tensorRT就提供了trtexec命令。

那么setup函数提供了entry_points和scripts这两个参数。它们的区别在于:

  • entry_points是把python文件中的函数自动生成为可执行脚本
  • scripts是把.sh、.py等可执行脚本生成到系统path中
from setuptools import setupsetup(…………# 把python中的函数自动生成为一个可执行的脚本# 如下:把fool.main文件中的main函数自动生成为一个可执行脚本,可以通过命令foo执行该脚本entry_points={'console_scripts': [ # key值为console_scripts'foo = foo.main:main' # 格式为'命令名 = 模块名:函数名']},# 将 bin/foo.sh 和 bar.py 脚本,生成到系统 PATH中# 执行 python setup.py install 后# 会生成 如 /usr/bin/foo.sh 和 如 /usr/bin/bar.pyscripts=['bin/foo.sh', 'bar.py']
)

8.C/C++ 扩展

python开发成本低,但运行速度堪忧,c/c++速度无人能敌,但内存管理、模板编程等对程序猿来说,开发成本太高。那么python+c的混合编程的解决方案应运而生,博采众长,python就像前端,简单的语言赏心悦目,c就像后端,适合做计算密集型任务。而且c/c++还可以有效规避掉python的GIL锁,速度更上一层楼。

python+c/c++混合编程的技术路径有很多,如:

  • 原生的Python.h
  • cython
  • ctypes、cffi
  • SWIG
  • Boost.Python:逐渐被pybind11取代
  • pybind11:目前比较流行推荐的方法,学习成本低,pytorch也采用了该方法。

本文还是主要介绍setuptools是如何进行c/c++ 扩展。

编译c/c++拓展源码的命令为:python setup.py build_ext --inplace。或者直接python setup.py build该命令包括了build_ext步骤。那么我们该如何指导编译器编译c/c++源码呢。

本质上setuptools是根据setup.py配置来指导生成gcc命令行,当然你也可以粗暴地直接用gcc命令行来编译c/c++拓展源码,但工程量太大,setuptools支持很多混合编程技术cython、SWIG等等。所以甭管你采用什么混合编程技术,绕不开setuptools。setuptools编译c/c++拓展源码的过程主要是把源代码编译成动态连接库(linux下是.so,windows下是.pyd)。这样就可以在.py中愉快import并使用拓展模块了。

主要看setup函数的ext_modules参数,该参数type为list[setuptools.Extension]。所以编译核心就在于这个setuptools.Extension类,该类只支持c/c++拓展,要实现cuda拓展需要自定义Extension类,如pytorch的CUDAExtension。

setuptools.Extension类有几个重要的构造参数(详见API文档)

  • name:在python中import该拓展的名称
  • sources:源代码文件名
  • language:默认'c',如果要用C++,改成'c++'
  • include_dirs:其实就是传递给 gcc 的 -I(大写i)指定include的头文件目录
  • library_dirs:其实就是传递给 gcc 的 -L 指定连接文件的目录
  • libraries:其实就是传给 gcc 的 -l(小写的L)指定连接文件,在L指定的位置找
  • extra_compile_args:其实传给 gcc 的额外的编译参数,比方'-std=c++11'
  • extra_link_args:其实传给 gcc 的额外的链接参数(生成动态链接库)
  • define_macros:定义宏
  • undef_macros:取消定义宏

举个例子:

from setuptools import setup,Extensionsetup(ext_modules=[Extension(name='foo',  # type=str。并且还支持层级命名,如myapp.foosources=['foo/csrc/foo1.c','foo/csrc/foo2.c'],  # type=list[str]。源代码的文件名,可以用glob.glob查找所有.c文件include_dirs=['foo'], # type=list[str]。拓展include头文件,相当于传递给gcc -I )])

8.1宏预处理

setuptools.Extension用define_macros 和 undef_macros构造参数来定义或取消定义宏。define_macros的type为list[tuple( name:str , value:str|None )] 。值为 None 的宏 FOO 等价于#define FOO ,否则等价于# define FOO value值 。undef_macros 同理,等价于#undef FOO 。

举个例子:陈鸣:来编写你的 setup 脚本(二)

Extension(define_macros=[('NDEBUG', '1'),('HAVE_STRFTIME', None)],undef_macros=['HAVE_FOO', 'HAVE_BAR'])

上面的代码相当于在每个C文件前加上了:

#define NDEBUG 1
#define HAVE_STRFTIME
#undef HAVE_FOO
#undef HAVE_BAR

9.自定义命令行为

自定义命令行为是setuptools进阶知识。setuptools包括许多命令,如下:

Standard commands:build             build everything needed to installbuild_py          "build" pure Python modules (copy to build directory)build_ext         build C/C++ extensions (compile/link to build directory)build_clib        build C/C++ libraries used by Python extensionsbuild_scripts     "build" scripts (copy and fixup #! line)clean             clean up temporary files from 'build' commandinstall           install everything from build directoryinstall_lib       install all Python modules (extensions and pure Python)install_headers   install C/C++ header filesinstall_scripts   install scripts (Python or otherwise)install_data      install data filessdist             create a source distribution (tarball, zip file, etc.)register          register the distribution with the Python package indexbdist             create a built (binary) distributionbdist_dumb        create a "dumb" built distributionbdist_rpm         create an RPM distributionbdist_wininst     create an executable installer for MS Windowsupload            upload binary package to PyPI
Extra commands:见:https://pythonhosted.org/an_example_pypi_project/setuptools.html

这些命令具体是由定义在setuptools.command中的类执行的。比如python setup.py bdist由setuptools.command.bdist类来执行。因此我们可以继承于setuptools.command中的类来执行自定义的命令行为。比如pytorch的Build.Extension就继承于setuptools.command.build_ext。具体怎么继承并改写这个command类就需要阅读源码了。

继承完command类后,需要通过cmdclass参数告诉setuptools,该参数为一个字典,key为str命令名,value为继承于command类。

from setuptools import setup
import setuptools.command.build_ext as build_extclass BuildExtension(build_ext,object):…………setup(  cmdclass={'build_ext': BuildExtension}  )

参考:

  • python官方文档MANIFEST.in
  • 王炳明:花了两天,终于把 Python 的 setup.py 给整明白了
  • setuptools官方文档
  • 陈鸣:来编写你的 setup 脚本(二)

---------------------------------------

转载自:https://zhuanlan.zhihu.com/p/460233022

作者:深度学习可好玩了

python打包分发工具:setuptools相关推荐

  1. Python打包分发工具setuptools简介

    Python打包分发工具setuptools 通过这个工具,可以打包安装插件,并且还可以发布到PyPI上面,具体参考: 1.http://python.jobbole.com/87240/ 2.htt ...

  2. python 查看当前目录_「Python」打包分发工具setuptools学习

    ❝ setuptools是python标准的打包分发工具,它可以将我们编写的python项目打包安装,这样其他同事就可以像调用标准库或python第三方库那样直接使用:也可以将项目上传到Pypi供更多 ...

  3. Python打包分发

    文章目录 Python打包分发 1.包管理 2.打包的主要工具 3.使用setup.py打包 命令行操作打包 查询命令帮助 1.build命令,编译 2.install命令,安装 3.sdist命令 ...

  4. python打包加密工具:Pyinstaller和Nuitka

    python打包加密工具概述 参考链接 谈谈 Pyinstaller 的编译和反编译,如何保护你的代码 Linux之Python代码打包工具Nuitka使用说明 Nuitka-Python 打包 Li ...

  5. python 包管理工具 —— setuptools

    1. 创建一个简单的包 创建 setup_demo文件夹,编写如下的 setup.py 文件: from setuptools import setup, find_packages setup(na ...

  6. Setuptools(Python打包工具)

    目录 前言 一.安装 二.源码包介绍 2-1.源码包sdist 2-1-1.打包成源码包sdist 2-1-2.安装源码包 2-2.二进制包bdist 2-2-1.打包成源码包bdist 2-2-2. ...

  7. python一键打包工具setuptools

    分发工具setuptools 一般 Python 安装会自带 setuptools,如果没有可以使用 pip 安装:(注意:python2,python3都安装了,若要使用python3,需要使用pi ...

  8. python打包和添加数据文件_python库打包分发setup.py编写指南

    python库打包分发setup.py编写指南 python之所以强大,在于有许许多多的人贡献自己的力量,他们将自己开发的项目打包上传至pypi,这使得python社区有取之不尽用之不竭的第三方库.工 ...

  9. Python打包发布

    python打包分发 ================== 打包工具:setuptools,wheel ------------------ ###操作顺序: ####1. 项目配置准备,主要是在项目 ...

最新文章

  1. 低耗时、高精度,微软提基于半监督学习的神经网络结构搜索算法
  2. Hazelcast集群服务(2)
  3. 迅为linux下串口,迅为iMX6UL开发板多路串口开发板接口详解
  4. 新媒体学python有用吗_你真的不学Python吗?学习Python的四大理由!
  5. CodeMirror 5.46.0 发布,多功能在线代码编辑器
  6. 用 pandas + matplotlib 绘制精美的K线图
  7. 注释和简单用户交互程序
  8. stm32f407能跑linux吗_跑步能跑进医院?那我该做跑步运动吗?想健康一点太难了...
  9. arm shellcode 编写详析1
  10. 如何通过破解hash来获取管理员密码(转)
  11. 计算机视觉(CV)中图像的梯度
  12. 为ashx文件启用session管理
  13. 【云原生 | Docker篇】 Docker容器配置阿里云镜像加速器
  14. linux 文件夹 775,Linux chmod目录权限命令图文详解
  15. pycharm中配置Git教程
  16. Ubuntu下F2FS文件系统的安装与挂载
  17. LTE技术对PTN的影响
  18. AT89S52单片机之硬件存储结构
  19. python设计贪吃蛇游戏论文_150行python代码实现贪吃蛇游戏
  20. 前端JavaScript学习网站(重磅推荐)

热门文章

  1. “内卷”之下,本土主题公园路在何方?
  2. FileMaker 实时定位当前地址
  3. 【LeetCode】合并石头的最低成本 [H](动态规划)
  4. zabbix通过SNMPv2监控DELL服务器的硬件
  5. 潇洒塑胶模具设计培训学校注塑模具设计培训课堂小知识
  6. ie8 css占满剩余空间,让IE7 IE8支持CSS3 background-size属性
  7. 高德地图查询各省地市县区地理坐标
  8. 如何提高自己的自信心
  9. “江一燕体”被群嘲,star(明星)卖人设有多招人烦?
  10. 硬盘录像机开机无显示