用Python写过不少的脚本,现在要把脚本打包成模块并进行发布,然后才明白脚本Boy到正规的码农差距有多大= =。 踩了很多天的坑之后,自己学习到了Python的包分发机制,以及如何利用Pypi向全世界分发自己的模块。现在简单地做一些整理。

Python包机制

包是一个模块或模块/子模块的集合,一般情况下被压缩到一个压缩包中。 其中包含

  1. 依赖信息
  2. 将文件拷贝到标准的包搜索路径的指令。
  3. 编译指令(如果在安装前代码必须被编译的话)。

也就是说,为了分发模块,我们需要把模块的依赖信息和模块一起打包。在Python中,这个打包好的可分发的文件一般以.egg结尾,其作用可以理解为java中的jar。Python的包管理以及分发曾经经历过非常混乱的一段时期,但是如今已经基本稳定(或者说,流行?)为两个套件:

准备工作

注册Pypi

Pypi - the Python Package Index Pypi是Python语言包的仓库,全世界所有开源Python开发者都会在Pypi上提交&下载软件包

为了我们后续的提交操作,我们需要首先注册一个Pypi的账号,注册非常简单,提供用户名,密码以及邮箱,经过验证之后就注册完成了。

目录结构

Python没有严格的工程目录要求,只要有__init__.py在的地方,就会被认为是一个Python的包。但是出于方便协作考虑,可以把自己的源代码与各种脚本分开存放。具体的结构可以学习Github上比较流行的Python项目,选择自己喜欢的即可。

环境配置

首先你需要有pip,pip自从3.4版本开始已经随python内置发布,如果使用的版本比较低,可以自己手动进行安装:

sudo apt-get install python-pip

然后我们需要安装setuptools

pip install setuptools

编写安装脚本

准备工作就绪之后,我们就可以开始编写安装脚本了。

填写配置信息

基本框架

from setuptools import setup, find_packages # 引入setuptools包

setup(
    option = values, # 本质上是一个函数的参数,分行写便于维护
)

参数介绍

指定整个包

指定单独模块

依赖关系

包内数据

其他数据

Meta-Data

Meta-Data描述值
name包名
version此次发布版本
author作者名
author_email作者邮箱
maintainer维护者名
maintainer_email维护者邮箱
url主页
description简要描述
long_description详细描述
download_url下载地址
classifiers分类,参见此处
platforms平台列表
license授权协议

典型配置

from setuptools import setup, find_packages
setup(
    name = "HelloWorld",
    version = "0.1",
    packages = find_packages(),
    scripts = ['say_hello.py'],

    # Project uses reStructuredText, so ensure that the docutils get
    # installed or upgraded on the target machine
    install_requires = ['docutils>=0.3'],

    package_data = {
        # If any package contains *.txt or *.rst files, include them:
        '': ['*.txt', '*.rst'],
        # And include any *.msg files found in the 'hello' package, too:
        'hello': ['*.msg'],
    },

    # metadata for upload to PyPI
    author = "Me",
    author_email = "me@example.com",
    description = "This is an Example Package",
    license = "PSF",
    keywords = "hello world example examples",
    url = "http://example.com/HelloWorld/",   # project home page, if any

    # could also include long_description, download_url, classifiers, etc.
)

打包

这个坑踩了很久- -,没有老司机的带的痛苦 与开发环境不同的时候,当用户运行你的包时,使用open等命令是以当前目录为根运行的,所以你必须指定数据所在位置,否则会出现IOError甚至更糟糕的情况

指定需要分发的文件

自动处理

当没有MANIFEST.in文件时,Setuptools将会按照下面的原则处理文件:

  • 所有py_modulespackages选项包含的Python源文件
  • 所有ext_moduleslibraries选项指定的C源文件
  • scripts指定的脚本(参见Installing Scripts
  • 形如test/test*.py的文件
  • README.txt(或README),setup.pysetup.cfg
  • 符合package_data选项的所有文件(参见Installing Package Data
  • 符合data_files选项的所有文件(参见Installing Additional Files

翻译自Python官方文档 Specifying the files to distribute

手动处理

一般而言,自动处理已经足够,但是如果想要自己指定的话,则需要编辑MANIFEST.in模板文件。 MANIFEST.in模板文件很简单,每一行都导入或者导出表示符合正则的一类文件。比如说:

# 导入根目录下满足*.txt的文件
include *.txt
# 递归导入examples目录下满足*.txt和*.py的文件
recursive-include examples *.txt *.py
# 导入满足examples/sample?/build的文件夹下所有文件
prune examples/sample?/build

详细语法参见此处

注意: 当根目录下存在MANIFEST.in文件时,Setuptools将不会再采用自动处理的设定,因此需要在MANIFEST.in文件中指明所有需要导入的文件。

调用数据

当你需要调用Python包中的文件时,你可以使用下面的方法:

from pkg_resources import resource_string
data = resource_string(__name__, 'data.dat')

此时,指定的data.dat文件将会以二进制文件流的形式赋值到data变量中,你可以按照自己的需要进行进一步处理。比如说:

from pkg_resources import resource_string
    data = resource_string(__name__, 'example.yml')
    with open('config.yml', 'w') as f:
        f.write(str(data, encoding='utf-8'))
        f.close()

上述代码实现了从example.yml中读取数据并保存到config.yml文件中。

创建源码分发包

在包的根目录下执行:

python setup.py sdist

默认情况下,sdist命令将会为Unix创建gzip压缩文件,为Windows创建zip压缩文件 你也可以添加参数--formats=zip指定生成的文件类型,所有支持的参数见此处

源码分发似乎不会导入package_data中指定的数据,如果上传本分发包可能导致用户通过pip安装的包中没有需要的数据。

创建二进制分发包

除了创建源码分发之外,我们还可以创建基于平台的二进制分发包。 在包的根目录下执行:

python setup.py bdist

默认情况下,这个命令将会创建基于自身平台的分发包。 同样的,你也可以添加--format=zip参数来指定生成的文件,支持的参数见此处 除此之外,也可以使用以下的命令直接生成对应格式的分发包:

命令格式
bdist_dumbtar, gztar, bztar, xztar, ztar, zip
bdist_rpmrpm, srpm
bdist_wininstwininst
bdist_msimsi

这一命令无法跨平台, 在Linux上选择制作wininst分发包时会提示缺乏相应的支持。

创建wheel分发包

wheel是新出的分发格式,旨在取代egg,你可以通过下列命令进行安装:

pip install wheel

然后在包的根目录下执行:

python setup.ppy bdist_wheel

上传到Pypi

注册包

在上传我们的包之前,我们需要首先向Pypi提交包的相关信息。 在包的根目录下执行:

python setup.py register

没有登陆的话,需要进行登陆;如果已经登陆,直接回车使用默认设置即可。

上传包

注册完毕后,我们可以提交我们的包了。 在包的根目录下执行:

python setup.py sdist bdist_wininst upload

这条命令将会向Pypi提交源码和Win下的安装包,如果需要上传别的包,只要直接写出即可。

参考资料

更新日志

  • 2015年11月03日 初步完成
  • 2016年03月30日 修复了部分笔误,添加了一些注释,增加了wheel相关的内容