【其他】打包一个 python 项目
打包一个 python 项目
1. 项目的分发打包
对源代码进行封装,使使用者的安装部署更为简洁。
1 | pip install pwntools |
distutils
distutils
是 Python 的一个标准库,从命名上很容易看出它是一个分发(distribute)工具(utlis),它是 Python 官方开发的一个分发打包工具,所有后续的打包工具,全部都是基于它进行开发的。
通过一个 setup.py 来实现模块的封装与安装(参考链接:https://docs.python.org/3.6/distutils/setupscript.html):
1 | #!/usr/bin/python3 |
setuptools
setuptools
是 distutils 增强版,不包括在标准库中。setuptools 有一系列分支版本,包括 distribute
等。
在安装了 setuptools 之后,就可以使用 easy_install 来安装一个包了:
通过包名,从PyPI寻找最新版本,自动下载、编译、安装
1 | easy_install pkg_name |
通过包名从指定下载页寻找链接来安装或升级包
1 | $ easy_install -f http://pythonpaste.org/package_index.html |
指定线上的包地址安装
1 | $ easy_install http://example.com/path/to/MyPackage-1.2.3.tgz |
从本地的 .egg 文件安装
1 | $ easy_install xxx.egg |
在安装时你可以添加额外的参数
1 | 指定安装目录:--install-dir=DIR, -d DIR |
但是比起 easy_install,我更喜欢 pip。
2. setup.py 的编写
最简单的 setup.py
一个最简单的例子:如果我们想要创建一个名为 foo 的包,只包含一个文件 foo.py
,那么 setup.py 的写法如下:
1 | from distutils.core import setup |
setup()
函数的参数就是需要提供的信息。主要包含两种类型:
- 包的元数据:包名称,版本号等
- 包内文件:这个包中包含哪些文件内容,需要哪些依赖等等
当你编写好 setup.py 后,运行:
1 | python setup.py sdist |
这将会生成一个 foo-1.0.tar.gz
(或者 .zip
) 文件。
运行:
1 | python setup.py install |
将会执行安装。这会将 foo.py 复制到当前系统中 Python 的第三方模块目录下。
接下来看一个更复杂的例子:
1 | #!/usr/bin/env python |
和前面的简单例子的区别在于,这个 setup.py 的元数据比较多。其他的元数据信息可以看官方文档。
下面会反复提到 package 和 module 的概念,这里作一个解释:
- module:每一个 python 文件都是一个 module
- package:本质上是一个目录,装有 module 的目录就是一个 package。原则上,一个 package(目录)中必须有一个
__init__.py
文件来表示该目录是一个 package。下面提到的
模块
和包
都是指 module 和 package,我也尽量用英文单词。
打包哪些 python 文件?
在打包时,必须告诉 distutils 哪些源文件(.py)是需要被打包的。这个时候就有两种方式:
- 以 package 为单位打包:适合更大的项目,有多个 package 管理的情况下使用;
- 以 module 为单位打包:适合小项目,只有若干个源文件的情况下使用。
packages 参数: 列出所有需要的 package
packages 是一个 list,每一个元素都是一个 package 名,distutils 会分别查找这些 package,将其中的 python 文件(也就是 module)进行打包。
为了实现这一点,需要有一个 package 名与其目录的对应关系。当你输入了类似 package=["foo"]
这样的语句时,表示在 setup.py 同目录下存在一个名为 foo 的目录,且存在 foo/__init__.py
文件来表示 foo 是一个package。
当然,如果你使用的 package 的位置不在当前目录下,那么可以使用 package_dir
参数来指定,类似于 package_dir = {'': 'lib'}
,它是一个字典,键表示的是包名,值表示的是当前目录距离包所在目录的相对位置。这个例子的意思是,包 foo 位于 lib/foo/__init__.py
。
py_modules 参数: 列出独立的模块
也可以直接引用单独的 module。如果只有一个 module,尤其是只有单个源文件的时候,使用 py_modules 更好。
1 | py_modules = ['mod1', 'pkg.mod2'] |
上面这句话描述了两个 module:一个位于包的根目录下,另一个位于名为 pkg 的包中,模块名为 mod2。
如何注册一个命令行脚本?
一般情况下,setup.py 安装一个包后,只能通过 import xxx
的形式在 python 源代码里使用。但是也可以把某个脚本注册到命令行下。
被注册的脚本需要是一个 python 脚本,最好在脚本开头写上 #!python
。
1 | setup(..., |
这样,将会把 scripts/xmlproc_parse
和 scripts/xmlproc_val
添加到环境变量,可以直接输入 xmlproc_parse
和 xmlproc_val
来执行这两个脚本。
还有一种方法是使用 setuptools,这样就可以使用 entry_points
:
1 | setup(..., |
这样就会在环境变量中添加一个 foo
命令,它指向的是 foo/main.py
中的 main 函数。
3. argparse 的使用
官方文档 (终于有中文文档了)
创建一个解析器:
1 | parser = argparse.ArgumentParser(description='Process some integers.') |
添加参数:
给一个 ArgumentParser
添加程序参数信息是通过调用 add_argument()
方法完成的。通常,这些调用指定 ArgumentParser
如何获取命令行字符串并将其转换为对象。这些信息在 parse_args()
调用时被存储和使用。
参数有必选参数和可选参数两种:
- 必选参数:类似于前面例子中的
integers
,即参数名不加-
的参数。 - 可选参数:类似于前面例子中的
--sum
,即参数名添加了-
的参数。
必选参数必须被指定。否则会报错。(可选参数也可以使用 required=True
来指定,使其成为一个必须的参数。)
普通参数是通过顺序来区分的,而可选参数则是通过类似 --name="abc"
这样的形式传递,增加了命令行中的可读性。
下面是一个例子
1 | import argparse |
nargs='+'
表示传入的参数个数,其中+表示至少传入一个参数。default='三'
表示默认值是'三'
前面的例子中,必选参数
integers
和 可选参数--name
都是为了传递一个(或多个)变量,但有时候,我们希望可选参数来表示一个不同的操作,例如:
1 | import argparse |
这个例子将接收若干个整数,输出他们的最大值。如果设置了 --sum
参数,则会输出它们的和。这个依靠 action
参数。(参考链接:https://blog.csdn.net/Drievn/article/details/70821188)
解释 action
参数:在解析到这个参数时,触发某种动作。argparse 内置了六种动作:
"store"
:将参数的传递的值原本地保存下来"store_const"
:将保存一个固定值,这个固定值由const=xxx
来指定"store_true"
/"store_false"
:保存 true / false"append"
:将参数的值 append 到一个 list 中append_const
: 将固定的值 append 到一个 list 中
上述六个操作,都会将值保存到 dest=xxx
指定的位置。
1 | import argparse |