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

功能亮点

除了去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包。
有许多方式来实现自定义安装,下面列出了最简单和最有意义的方式。


基本使用

对于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特性。

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脚本会检查到这些,并且以错误退出。