日常工作中,我们经常为更方便地建模,会自己封装一些函数,从而在每次导入时能更方便。这种方式的话,需要先将自定的包导入到系统环境中去,然后再进行引用,具体代码如下所示:

  1. import sys
  2. sys.path.append("/home/yumingmin/itools")
  3. from db.impala_hive import ImpalaHiveRunner

通过上述方式,确实可以把大大提高代码的复用性,但是也会有一个问题存在,就是每次调用需要先使用 sys 库添加自定义包,而且如果你封装的函数要给同事使用的话,就必须把文件传给他。我们期望的是,能够将自定义库进行打包后,能够通过 pip 直接进行安装,并且可以像 Pandas、NumPy 一样很方便的使用。

接下来,我们就来事先这样一个包 itools,最终达到的逾期效果如下,让我们是马上开始吧。

  1. from itools.db.impala_hive import ImpalaHiveRunner

1.1 项目结构

所需要打包的 itools 文件目录结构具体如下:

  1. $ tree itools
  2. itools
  3. ├── itools
  4. ├── db
  5. ├── check.py
  6. ├── done.py
  7. ├── hdfs.py
  8. ├── impala_hive.py
  9. ├── __init__.py
  10. └── mysql.py
  11. ├── feature
  12. ├── build_features.py
  13. ├── eda.py
  14. └── __init__.py
  15. ├── __init__.py
  16. └── resources
  17. ├── pics
  18. ├── p1.jpg
  19. └── p2.jpg
  20. └── sqls
  21. └── tmp.sql
  22. ├── README.md
  23. ├── setup.py
  24. └── LICENSE

1.2 编写setup.py文件

上述文件目录中,我们已经添加了打包文件 📝setup.py,我们来看下具体文件中有什么内容:

  1. from setuptools import setup, find_packages
  2. setup(
  3. name="itools",
  4. version="0.1.0",
  5. packages=find_packages(),
  6. install_requires=["requests", "hdfs", "tqdm"],
  7. include_package_data=True,
  8. url="http://git.ppdaicorp.com/yumingmin/itools",
  9. license='GPL',
  10. author="yumingmin",
  11. author_email="yumingmin@xinye.com",
  12. description="Python tools for ml",
  13. platforms="any",
  14. entry_points={
  15. "console_scripts": ["i2s=itools.i2s:main"]
  16. }
  17. )

接下来然后我们看下这些参数的含义:

参数 解释
name 包名称。注意:如果需要上传到 pypi,则不可以与已有的项目相同
version 包版本
author 程序的作者
author_email 程序作者的邮箱
maintainer 维护者
maintainer_email 维护者的邮箱地址
url 程序的官网地址
license 授权信息
description 简单描述
long_description 详细描述
platforms 程序适用的平台列表
classifiers 程序所属分类列表
keywords 程序关键字列表
packages 需要打包的目录列表,可以手动指定,也可以直接用 find_packages 函数
py_modules 需要打包的Python文件列表
download_url 程序下载地址
cmdclass 添加自定义命令
data_files 打包时需要打包的数据文件,如图片、配置文件得分
scripts 安装时需要执行的脚本列表
install_requires 需要安装的依赖包
include_package_data 是否自动包含包内所有受版本控制(cvs/svn/git)的数据文件,默认True
entry_points 命令行工具,用来支持自动生成 cli 命令
keywords 关键词
python_requires Python版本
ext_modules 是一个包含extension实例的列表,extension的定义也有一些参数
ext_package 定义extension的相对路径
provides 定义可以为哪些模块提供依赖
project_urls 项目相关 urls
package_dir 指定哪些目录下的文件被映射到哪个源码包
exclude_package_data include_package_data 为 True 时该选项用于排除部分文件
extras_require 当前包的高级/额外特性需要依赖的分发包
tests_require 在测试时需要使用的依赖包
setup_requires 指定运行 setup.py 文件本身所依赖的包
dependency_links 指定依赖包的下载地址
zip_safe 不压缩包,而是以目录的形式安装

1.2.1 find_packages

对于简单工程来说,手动添加 packages 参数比较容易。但是对于复杂的工程来说,可能会添加很多的包,这个时候手动添加就会变得很麻烦。setuptools 模块提供了一个 find_packages 函数,它默认会在 📝setup.py 文件同处目录下搜索各个还有 📝_init.py_ 的目录作为要添加的包。

  1. find_packages(where=".", exclude=(), include=("*",))

find_packages 函数参数做一下解释:

  • where 参数用于指定在哪个目录下搜索包
  • exclude 参数用于指定排除哪些包
  • include 参数用于指定要包含的包

默认情况下,📝setup.py 文件只在其所在的目录下搜索包。如果不用 find_packages,想要找到其他目录下的包,也可以设置 package_dir 参数,其指定哪些目录下的文件被映射到哪个源码包,如:package_dir={"": "src"} 表示 root_package 中的模块都在 src 目录中。

1.2.2 package_data

该参数是一个从包名称到 glob 模式列表的字典。如果数据文件包含在包的子目录中,则 glob 可以包括子目录名称,其格式一般为:{"package_name": ["files"]}。比如:

  1. package_data = {"mypkg": ["data/*.dat"]}

1.2.3 include_package_data

该参数被设置为 True 时自动添加包中受版本控制的数据文件,可替代 package_data。同时,exclude_package_data 可以排除某些文件。注意:当需要加入没有被版本控制的文件时,还是仍然需要使用 package_data 参数才行。

1.2.4 data_files

该参数通常用于包含不在包内的数据文件,即包的外部文件,如:配置文件、消息目录、数据文件。其指定了一系列二元组,即(目的安装目录, 源文件),表示哪些文件被安装到哪些目录中。如果目录名是相对路径,则相对于安装前缀进行解释。

1.2.5 manifest template

manifest tempate 即编写 📝MANIFEST.in 文件,文件内容就是需要包含在分发包中的文件。一个 MANIFEST.IN 文件如下:

  1. include *.txt
  2. recursive-include itools/resources *.sql *.default *.ttc *.jpg
  3. prune examples/sample?/build

MANIFEST.in 文件的编写规则可参考:https://docs.python.org/3.6/distutils/sourcedist.html

1.2.6 生成脚本

有两个参数:scriptsconsole_scripts,可用于生成脚本。

1.2.6.1 console_scripts

entry_points 参数用来支持自动生成脚本,其值应该为一个字典,从 entry_points 组名映射到一个表示 entry_points 的字符串或字符串列表,如:

  1. setup(
  2. ...,
  3. entry_points={
  4. "console_scripts": ["i2s=itools.i2s:main"]
  5. }
  6. )

当然你也可以在 console_scripts 同时定义两个命令:

  1. setup(
  2. ...,
  3. entry_points={
  4. "console_scripts": [
  5. "i2s=itools.i2s:main",
  6. "i2ss=itools.i2s:main1",
  7. ]
  8. }
  9. )

1.2.6.2 scripts

scripts 参数是一个列表,安装包时在该参数中列出的文件会被安装到系统 PATH 路径下。如:

  1. scripts = ["/bin/foo.sh", "bar.py"]

使用下面的方法可以将脚本重命名,去掉脚本文件的扩展名(.py、.sh):

  1. from setuptools.command.install_scripts import install_scripts
  2. class InstallScripts(install_scripts):
  3. def run(self):
  4. install_scripts.run(self)
  5. # Rename some script files
  6. for script in self.get_outputs():
  7. if basename.endswith(".py") or basename.endswith(".sh"):
  8. dest = script[:-3]
  9. else:
  10. continue
  11. print("moving %s to %s" % (script, dest))
  12. shutil.move(script, dest)
  13. setup(
  14. # other arguments here...
  15. cmdclass={
  16. "install_scripts": InstallScripts
  17. }
  18. )

1.2.7 ext_modules

ext_modules 参数用于构建 C 和 C++ 扩展包,它是 Extension 实例的列表,每一个 Extension 示例描述了一个独立的扩展模块,扩展模块可以设置扩展包名,头文件、源文件、链接库及其路径、宏定义和编辑参数等。如:

  1. from setuptools import Extension
  2. setup(
  3. # other arguments here...
  4. ext_modules=[
  5. Extension('foo',
  6. glob(path.join(here, 'src', '*.c')),
  7. libraries = [ 'rt' ],
  8. include_dirs=[numpy.get_include()])
  9. ]
  10. )

详细可参考:https://docs.python.org/3.6/distutils/setupscript.html#preprocessor-options

1.2.8 zip_safe

zip_safe 参数决定包是否作为一个 zip 压缩后的 egg 文件安装,还是作为一个以 .egg 结尾的目录安装。因为有些工具不支持 zip 压缩文件,而且压缩后的包也不方便调试,所以建议将其设为 False,即 zip_safe=False

1.2.9 自定义命令

setup.py 文件有很多内置的命令,可以使用 python setup.py --help-commands 查看。如果想要定制自己需要的命令,可以添加 cmdclass 参数,其值为一个字典。实现自定义命名需要继承 setuptools.Command 或者 disutils.core.Command,并重写 run 方法。

  1. from setuptools import setup, Command
  2. class InstallCommand(Command):
  3. description = "Installs the foo."
  4. user_options = [
  5. ('foo=', None, 'Specify the foo to bar.'),
  6. ]
  7. def initialize_options(self):
  8. self.foo = None
  9. def finalize_options(self):
  10. assert self.foo in (None, 'myFoo', 'myFoo2'), 'Invalid foo!'
  11. def run(self):
  12. install_all_the_things()
  13. setup(
  14. ...,
  15. cmdclass={
  16. 'install': InstallCommand,
  17. }
  18. )

1.2.10 依赖关系

如果包依赖其他的包,可以指定 install_requires 参数,其值为一个列表,如:

  1. install_requires=[
  2. 'requests',
  3. 'flask>=1.0'
  4. 'setuptools==38.2.4',
  5. 'django>=1.11, !=1.11.1, <=2',
  6. 'requests[security, socks]>=2.18.4',
  7. ]

指定该参数后,在安装包时自动从 pypi 仓库中下载指定的依赖包进行安装。

可能包中的某些特殊的、偏门的功能,大多数情况下不会被用到,那么这些功能的依赖,就不适合放在 install_requires 里。这时就可以用 extras_require 来指定可选的功能和依赖,如:

  1. extras_require={
  2. 'security': ['pyOpenSSL>=0.14', 'cryptography>=1.3.4', 'idna>=2.0.0'],
  3. 'socks': ['PySocks>=1.5.6, !=1.5.7'],
  4. }

还可以指定测试时或者执行 setup.py 时的依赖包,如:

  1. tests_require=[
  2. 'pytest>=3.3.1',
  3. 'pytest-cov>=2.5.1',
  4. ],
  5. setup_requires=[
  6. 'pytest-runner>=3.0',
  7. ]

此外,还支持从指定链接下载依赖,即指定 dependency_links 参数,如:

  1. dependency_links = [
  2. "http://packages.example.com/snapshots/foo-1.0.tar.gz",
  3. "http://example2.com/p/bar-1.0.tar.gz",
  4. ]

可以用 python_requires 来指定运行时需要的 Python 版本,如包只支持在 Python 3.6 及机上版本运行,则可以指定:

  1. python_requires='>=3.6'

1.2.11 分类信息

classifiers 参数说明包的分类信息。所有支持的分类列表见:https://pypi.org/pypi?%3Aaction=list_classifiers,如:

  1. classifiers = [
  2. # 发展时期,常见的如下
  3. # 3 - Alpha
  4. # 4 - Beta
  5. # 5 - Production/Stable
  6. 'Development Status :: 3 - Alpha',
  7. # 开发的目标用户
  8. 'Intended Audience :: Developers',
  9. # 属于什么类型
  10. 'Topic :: Software Development :: Build Tools',
  11. # 许可证信息
  12. 'License :: OSI Approved :: MIT License',
  13. # 目标 Python 版本
  14. 'Programming Language :: Python :: 2',
  15. 'Programming Language :: Python :: 2.7',
  16. 'Programming Language :: Python :: 3',
  17. 'Programming Language :: Python :: 3.3',
  18. 'Programming Language :: Python :: 3.4',
  19. 'Programming Language :: Python :: 3.5',
  20. ]

1.3 setup.py 命令

setup.py 文件有很多内置命令可供使用,查看所有支持的命令:

  1. python setup.py --help-commands

常见的命令:

命令 解释
build 构建安装时所需的所有内容
build_ext 构建扩展,如用 C/C++, Cython 等编写的扩展,在调试时通常加 --inplace 参数,表示原地编译,即生成的扩展与源文件在同样的位置。
sdist 构建源码分发包,在 Windows 下为 zip 格式,Linux 下为 tag.gz 格式。执行 sdist 命令时,默认会被打包的文件:
- 所有 py_modules 或 packages 指定的源码文件
- 所有 ext_modules 指定的文件
- 所有 package_data 或 data_files 指定的文件
- 所有 scripts 指定的脚本文件
- README、README.txt、setup.py 和 setup.cfg文件

该命令构建的包主要用于发布,例如上传到 pypi 上。 | | bdist | 构建一个二进制的分发包。 | | bdist_egg | 构建一个 egg 分发包,经常用来替代基于 bdist 生成的模式 | | bdist_wheel | 构建一个 wheel 分发包,egg 包是过时的,whl 包是新的标准 | | install | 安装包到系统环境中。 | | develop | 以开发方式安装包,该命名不会真正的安装包,而是在系统环境中创建一个软链接指向包实际所在目录。这边在修改包之后不用再安装就能生效,便于调试 | | register / upload | 用于包的上传发布 |

1.4 setup.cfg

setup.cfg 文件用于提供 setup.py 的默认参数,详细的书写规则可参考:https://docs.python.org/3/distutils/configfile.html

1.5 版本命名

包版本的命名格式应为如下形式:

  1. N.N[.N]+[{a|b|c|rc}N[.N]+][.postN][.devN]

从左向右做一个简单的解释:

  • N.N:必须的部分,两个 N 分别代表了主版本和副版本号
  • [.N]:次要版本号,可以有零或多个
  • {a|b|c|rc}:阶段代号,可选, a, b, c, rc 分别代表 alpha, beta, candidate 和 release candidate
  • N[.N]:阶段版本号,如果提供,则至少有一位主版本号,后面可以加无限多位的副版本号
  • .postN:发行后更新版本号,可选
  • .devN:开发期间的发行版本号,可选

    1.6 打包

    注意:以下所有的操作都需要在根目录下执行

检查项目是否没有问题:

  1. python setup.py check

1.6.1 打包egg

将自定义库打包成 egg 文件:

  1. python setup.py bdist_egg

完成打包后,会在 📂 dist 文件夹生成一个 📝 itools-0.1.0-py3-none-any.egg 文件,可以直通根据这个 egg 文件就可以安装:

  1. easy_install itools-0.1.0-py3-none-any.egg

1.6.2 打包exe

可以打包成 EXE 程序,用以 Windows 下的分发。

  1. python setup.py bdist_wininst

1.6.3 打包zip、tar.gz

  1. python setup.py sdist

1.6.4 打包成whl

将程序打包成 wheel 文件,完成打包后会在 📂 dist 文件夹生成一个 📝 itools-0.1.0-py3-none-any.whl 文件。

  1. python setup.py bdist_wheel

通过 pip 直接进行安装:

  1. pip install itools-0.1.0-py3-none-any.whl

1.6.5 直接源代码安装

  1. python setup.py install

1.7 发布包至PyPI

发布包可以直接使用 setup.py 自带的命令,也可以使用 twine 工具。

1.7.1 twine

twine 是 PyPI 官方提供的工具,其特点是:

  • 验证 HTTPS 连接
  • 上传不需要执行 setup.py
  • 上传已经创建的文件,允许在发布前测试发行版
  • 支持上传任何包装格式

其包含三个子命令 checkregister 以及 upload。上传包时,直接用 upload 命令即可。

  1. 首先注册一个 PyPI 的账号,点击 https://pypi.org/ 进行创建
  2. 安装最新版本的 twine

    1. pip install -U twine
  3. 上传项目:使用 twine 上传项目的时候需要输入相应的 PyPI 的账号和密码。

    1. twine upload dist/*

    也可以建立一个账户验证文件 📝 ~/.pypirc,这样就不用上上传过程中输入账号和密码。注意:Windows 不可以,因为文件名中含有非法字符。 ```nginx [distutils] index-servers=pypi

[pypi] repository = https://upload.pypi.org/legacy/ username = yumingmin password =

  1. <a name="2loFF"></a>
  2. ## 1.7.2 setup.py自带工具
  3. 其他操作和上述一样,例如 .pypirc 文件以及创建账户,不同的是项目注册和发版:
  4. ```bash
  5. python setup.py register

该命令在 PyPi 上注册项目信息,成功注册之后,可以在 PyPi 上看到项目信息。最后构建源码包发布即可:

  1. # 发布源码包
  2. $ python setup.py sdist upload
  3. # 同时发布源码包和 whl 二进制包
  4. $ python setup.py sdist bdist_wheel upload

1.8 easy_install 与 pip

easy_insallsetuptools 包提供的第三方包安装工具,而 pip 是 Python 中一个功能完备的包管理工具,是 easy_install 的改进版,提供更好的提示信息,删除包等功能。

pip 相对于 easy_install 进行了以下几个方面的改进:

https://stackoverflow.com/questions/4938738/use-argparse-to-run-1-of-2-functions-in-my-script
https://realpython.com/command-line-interfaces-python-argparse/
https://blog.csdn.net/qq_41629756/article/details/105689494
https://my.oschina.net/kuanghy/blog/703594
https://tendcode.com/article/python-shell/
https://www.cnblogs.com/shmily2018/p/11592448.html
https://blog.csdn.net/chengxuyuanyonghu/article/details/59716405