使用Setuptools构建和分发python包

Setuptoolsdisutils(python2.6+)的增强版,它使得开发者可以更容易地构建和分发python包,尤其是依赖其他包的包。
对用户来说,使用Setuptools构建和分发的包看起来就像普通的基于distuils的python包。为了使用Setuptools,用户不必安装或了解它们,开发者也不必在发行版中包含全部的Setuptools工具。通过包含一个单独的bootstrap模块,如果用户使用源码构建包,并且没有安装相应版本的Setuptools,那么包会自动的下载和安装它。

功能亮点

  • 使用easy_install工具的时候,在构建期间会自动的寻找/下载/安装/升级依赖包。easy_install支持通过HTTP,FTP,Subversion和SourceForge下载包,并且会自动的浏览PYPI上链接的网页,来寻找下载链接(它是当前python中最象CPAN的工具)。
  • 创建python egg文件 - 一个单文件,可导入的发行格式。
  • 增强对访问寄宿于zip包中的数据文件的支持。
  • 自动的包含源代码树中所有的包,而不必在setup.py中单独的列出它们。
  • 自动的包含源发行版中所有相关的文件,而不必创建一个MANIFEST.in文件,并且在源代码树发生改变的时候,不必重新生成MANIFEST文件。
  • 自动生成项目中任意数量的main函数的包装脚本或windows上的.exe文件(注意:它不是py2exe的替代,.exe文件依赖于本地的python)。
  • 透明的Pyrex支持,以便在setup.py中能够列出.pyx文件,甚至当终端用户没有安装Pyrex的时候,仍然可以工作(只要在源发行版中包含了Pyrex生成的C)。
  • 命令别名 - 为经常使用的命令和选项,创建具体项目的,每个用户的,或者站点级别的简称。
  • PYPI上传支持 - 把源发新版和egg包上传到PYPI。
  • 以“开发模式部署项目”,以便项目在sys.path上是可用的,并且仍然能够直接的编辑它的源代码。
  • 很容易的使用新的命令或setup()参数来扩展disutils,在多个项目之间分发和重用扩展,而不必拷贝代码。
  • 在项目的setup脚本中使用简单的“entry points”声明,创建可扩展的应用程序和自动发现扩展的框架。

除了去PYPI下载,setuptools的开发版本也可以从Python SVN sandbox下载,正在开发的0.6分支版本也是可用的。


安装setuptools

请按照EasyInstall安装说明来安装当前的setuptools的稳定版本。特别是,如果想要把包安装到Python的site-packages目录之外的地方,那么确保阅读自定义安装位置区域。
如果想要安装当前的setuptools正在开发中的版本,首先应该安装一个稳定版本,然后运行:
ez_setup.py setuptools==dev
这会从Python Subversion sandbox下载和安装setuptools的最新的开发版。

自定义安装位置:
默认情况下,EasyInstall会把包安装到Python的主site-packages目录,然后通过相同的目录下的一个自定义的.pth文件来管理它们。
但是经常有这样的需求:用户或开发者想使用easy_install在一个替代的位置来安装和管理Python包。
有许多方式来实现自定义安装,下面列出了最简单和最有意义的方式。

  • 使用-user选项
    从Python2.6开始支持用户模式,它意味着所有的Python版本都支持一个针对用户的替代的安装位置。默认的路径是site.USER_BASE变量的值。这种安装模式可以通过给setup.py installeasy_install指定--user选项来开启。这种方式满足了把包安装到用户相关的位置的需求。
  • 使用--user选项和自定义的PYTHONUSERBASE环境变量
    用户模式安装路径可以通过设置PYTHONUSERBASE环境变量来定制,它会更新site.USER_BASE的值,为了在特定的应用程序之间隔离包,可以简单地把那个应用程序的PYTHONUSERBASE环境变量设置为一个具体的值。
  • 使用virtualenv
    virtualenv是一个第三方包,它实际上“克隆”了一个python安装,从而创建了一个用于安装python包的隔离的路径。virtualenv的发展始于用户安装模式存在之前。virtualenv提供了一个受限制于被克隆的python安装的easy_install,并且这个easy_install以正常的方式来使用。virtualenv确实提供了许多用户安装模式没有提供的特性,比如:virtualenv能够隐藏全局的site-packages
    请阅读virtualenv的文档来获得更多的细节。

基本使用

对于setuptools的基本使用,只需要从setuptools,而不是distutils导入一些东西,下面是一个使用setuptools的最小的setup脚本:

from setuptools import setup, find_packages  
setup(  
    name = "HelloWorld",
    version = "0.1",
    packages = find_packages(),
)


如你所见,在项目中使用setuptools,不会花费更多的力气,只需要做上面的事情,项目就能够生成egg,上传到PYPI,和自动地包含setup.py所在的目录中的所有的包。
当然,在把项目发布到PYPI之前,可以给setup脚本添加更多的信息,来帮助人们发现和了解项目。并且项目可能包含一些依赖,一些数据文件和脚本:

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.
)


下面,我们会解释这些setup()参数中的大多数是做什么的(除了用于上传到PYPI的metadata),你可以在自己的项目中以多种方式来使用它们。


新增或改变的setup()参数

下面的setup()的关键字参数,是setuptools添加的或改变的,它们中所有的都是可选的。你不必支持它们,除非你想要使用相关联的setuptools特性。

  • include_package_data
    distutils一直都允许安装“数据文件”,这些数据文件会被安装到平台相关的位置。和包一起分发的数据文件的最常用的使用情形是通过在包目录中包含数据文件的方式,给包使用。
    setuptools提供了三种方式来指定要被包含在包中的数据文件,首先可以简单的使用include_package_data关键字参数。
    如果include_package_data被设置为True,这会告诉setuptools自动的包含它在包目录下发现的任何数据文件,这些数据文件必须在CVS或Subversion控制之下,或者它们必须通过distutils的MANIFEST.in文件被指定。
    MANIFEST.in文件是一个manifest template,定义如何生成MANIFEST文件,内容就是需要包含在分发包中的文件,一个MANIFEST.in文件如下:
    include *.txt
    recursive-include examples *.txt *.py
    prune examples/sample?/build

  • exclude_package_data
    一个映射包名称到glob模式列表的字典,包目录内文件名匹配任何glob模式的文件都不会被包含,可以使用它来移除任何被include_package_data包含的文件。

  • package_data
    如果数据文件不在版本控制之下,或者不在一个被支持的版本控制系统之下,或者想要细粒度地控制包含哪些文件,那么需要使用package_data关键字参数,比如:
from setuptools import setup, find_packages  
setup(  
    ...
    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'],
    }
)


package_data参数是一个映射包名称到glob模式列表的字典。如果数据文件被包含包的子目录中,glob模式可以包含子目录名称。比如,一个包目录树看起来如下:

setup.py  
src/  
    mypkg/
        __init__.py
        mypkg.txt
        data/
            somefile.dat
            otherdata.dat


那么setuptools setup文件,可能是这样的:

from setuptools import setup, find_packages  
setup(  
    ...
    packages = find_packages('src'),  # include all packages under src
    package_dir = {'':'src'},   # tell distutils packages are under src

    package_data = {
        # If any package contains *.txt files, include them:
        '': ['*.txt'],
        # And include any *.dat files found in the 'data' subdirectory
        # of the 'mypkg' package, also:
        'mypkg': ['data/*.dat'],
    }
)


注意,在package_data参数中,如果在一个空字符串下列出模式,那么这些模式会被用于在所有的包(即使这些包列出了自己的模式)中寻找文件,因此在上面的例子中,mypkg.txt虽然没在mypkg的模式中列出,仍然会被包含。
也要注意:如果使用路径,那么必须使用/作为路径分隔符,即使是在windows系统上,Setuptools在build期间会自动的把斜线转换成平台相关的分隔符。
有时,单独使用include_package_datapackage_data选项,并不能满足精确地定义包含哪些文件。比如:想要在版本控制系统和源代码中包含README文件,但是在安装的时候不包含它们。因此Setuptools也提供了exclude_package_data选项,它允许你做这样的事情:

from setuptools import setup, find_packages  
setup(  
    ...
    packages = find_packages('src'),  # include all packages under src
    package_dir = {'':'src'},   # tell distutils packages are under src

    include_package_data = True,    # include everything in source control

    # ...but exclude README.txt from all packages
    exclude_package_data = { '': ['README.txt'] },
)


正如package_data选项一样,exclude_package_data是一个映射包名称到通配符模式列表的字典,在使用这个选项的时候,""的键会把给定的模式应用到所有的包,匹配这些模式的任何文件在安装的时候都会被排除掉,即使这些文件在package_data中被列了出来或者是作为使用include_package_data的结果被包含了进来。
总的来说,这三个选项允许你:

include_package_data
接受所有的被MANIFEST.in匹配的或者是在版本控制系统中找到的文件和目录。
package_data
指定额外的模式来匹配不被MANIFEST.in匹配的或者是不在版本控制系统中的文件和目录。
exclude_package_data
指定用于安装包的时候不应该被包含的数据文件和目录的模式。

注意:由于distutils构建过程的工作方式,一个在项目(的之前的版本)中包含,然后(之后的版本中)停止包含的数据文件,或许会成为项目构建目录中的“孤儿”,需要运行setup.py clean --all来完全的移除它们。如果用户和贡献者通过SVN来追踪项目的中间版本,那么对于他们来说,这可能是非常有用的:确保让他们知道,何时作出的移除之前包含的文件这个改变,以便他们能够运行setup.py clean --all


使用find_packages()

对于简单的项目,人工地向setup()packages参数添加包名就足够了。然而对于非常大的项目(比如Twisted, PEAK, Zope, Chandler等),保持包列表更新是一个非常大的负担。这就是setuptools.find_packages()做的事情。
find_packages()带一个源目录和两个包名称模式的列表(一个用来指定包含哪些包,另外一个用来指定排除哪些包)作为参数。如果省略了源目录,那么它的缺省值是setup脚本所在的目录。一些项目使用src或lib目录作为源代码树的根目录,因此这些项目应该使用src或lib作为find_packages()的第一个参数。(当然这样的项目在它们的setup()函数的参数里,也需要像package_dir = {'':'src'}这样的东西,但是那仅仅是普通的distutils需要的东西。)
无论如何,find_packages()会遍历目标目录,寻找Python包,并使用inclusion模式过滤。在Python3.2和之前的版本,一个包只有包含__init__.py文件的时候,才会被识别。最后exclusion模式会被应用来移除匹配的包。
inclusion和exclusion模式是可以包含通配符的包名称。比如find_packages(exclude=["*.tests"])会排除掉所有名称的最后一部分是tests的包。find_packages(exclude=["*.tests", "*.tests.*"])会排除掉名称为tests的包的任何子包。但是它不会排除顶层的tests包及其子包。事实上,如果根本不想要tests包,需要做这样的事情:
find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"])
在真实情况下,exclusion模式的意图是覆盖比这更简单的使用情形,像排除一个单独的,具体的包及其子包。
不管这些参数,find_packages()返回一个包名的列表,这个列表适用于作为setup()packages参数来使用。因此,它通常是setup脚本中设置packages()参数的最简单的方式。特别是,当项目增加了顶级包或子包的时候,不必去修改setup脚本。


eggsecutable脚本(可执行的egg脚本)

偶尔,有想要使一个.egg文件可以直接执行的情况。可以通过包含如下所示的entry point来完成这件事:

setup(  
    # other arguments here...
    entry_points = {
        'setuptools.installation': [
            'eggsecutable = my_package.some_module:main_func',
        ]
    }
)


任何从上面的setup脚本构建而来的egg,都会包含一个简短的开头,它从my_package.some_module导入并调用main_func(),在unix-like平台上可以通过/bin/sh或赋予这个.egg文件可执行权限的方式,来调用这个egg脚本。相应的版本的python的“长名称”一定要能在PATH中找到。也就是说,如果egg脚本是使用python2.3构建而来的,那么python2.3可执行程序必须出现在PATH的某一个目录中。
这个特性的主要意图是支持在非windows平台上setuptools自己的安装。但是对其他项目可能也是有用的。
注意:带有eggsecutable头的egg脚本不能重命名,或者是通过符号连接来调用,必须使用原始文件名来调用它们,为了确保这个,运行pkg_resource就可以知道正在使用的项目和版本。如果这个.egg文件被重命名了或者通过改变了它的文件名称的符号连接来调用,那么.egg脚本会检查到这些,并且以错误退出。


参考资料

感谢浏览tim chow的作品!

如果您喜欢,可以分享到: 更多

如果您有任何疑问或想要与tim chow进行交流

可点此给tim chow发信

如有问题,也可在下面留言: