使用Distutils包发布Python

         Distutils可以用来在Python环境中构建和安装额外的模块。新的模块可以是纯Python的,也可以是用C/C++写的扩展模块,或者可以是Python包,包中包含了由C和Python编写的模块。


一:Distutils简介

1.1概念和术语

         对于模块开发者以及需要安装模块的使用者来说,Distutils的使用都很简单,作为一个开发者,除了编写源码之外,还需要:

编写setup脚本(一般是setup.py);

编写一个setup配置文件(可选);

创建一个源码发布;

创建一个或多个构建(二进制)发布(可选);


         有些模块开发者在开发时不会考虑多个平台发布,所以就有了packagers的角色,它们从模块开发者那取得源码发布,然后在多个平台上面进行构建,并发布多个平台的构建版本。


1.2简单例子

         由python编写的setup脚本一般都非常简单。作为autoconf类型的配置脚本,setup脚本可以在构建和安装模块发布时运行多次。

         比如,如果需要发布一个叫做foo的模块,它包含在一个文件foo.py,那setup脚本可以这样写:

from distutils.core import setup

setup(name='foo',

      version='1.0',

      py_modules=['foo'],

      )

 setup函数的参数表示提供给Distutils的信息,这些参数分为两类:包的元数据(包名、版本号)以及包的信息(本例中是一个Python模块的列表);模块由模块名表示,而不是文件名(对于包和扩展而言也是这样);建议可以提供更多的元数据,比如你的名字,email地址和项目的URL地址。


         编写好setup.py之后,就可以创建该模块的源码发布了:

python setup.py sdist

对于Windows而言,命令是: 

setup.py sdist

sdist命令会创建一个archive 文件(比如Unix上的tar文件,Windows上的zip文件),它包含setup.py, foo.py。该archive文件命名为foo-1.0.tar.gz(zip),解压之后的目录名是foo-1.0。


         如果一个用户希望安装foo模块,他只需要下载foo-1.0.tar.gz,解压,进入foo-1.0目录,然后运行:

python setup.py install

 该命令最终会将foo.py复制到Python环境存放第三方模块的目录中。在linux环境下,运行该命令的输出是:

# python setup.py install

running install

running build

running build_py

creating build

creating build/lib

copying foo.py -> build/lib

running install_lib

copying build/lib/foo.py -> /usr/lib/python2.7/site-packages

byte-compiling /usr/lib/python2.7/site-packages/foo.py to foo.pyc

running install_egg_info

Writing /usr/lib/python2.7/site-packages/foo-1.0-py2.7.egg-info

该命令生成的文件是:

/usr/lib/python2.7/site-packages/foo-1.0-py2.7.egg-info

/usr/lib/python2.7/site-packages/foo.py

/usr/lib/python2.7/site-packages/foo.pyc


         这个简单的例子展示了Distutils的基本概念。第一,开发者和安装者有同样的用户接口,也就是setup脚本,但他们使用的Distutils命令不同,sdist命令几乎只有开发者使用,而install对于安装者更常用。


         如果希望使用者的使用尽可能的简单,则可以创建多个构建发布。比如,如果在Windows中,可以使用bdist_wininst命令创建一个exe安装文件,下面的命令会在当前目录中创建foo-1.0.win32.exe文件:

python setup.py bdist_wininst

其他的构建发布有RPM(由bdist_rpm命令实现),Solaris pkgtool(bdist_pkgtool),以及HP-UX swinstall (bdist_sdux)。

         比如,下面的命令将会创建RPM文件foo-1.0.noarch.rpm(bdist_rpm命令必须运行于基于RPM的系统,比如Red Hat Linux, SuSE Linux, Mandrake Linux):

python setup.py bdist_rpm

可以通过下面的命令得到当前支持的发布格式:

python setup.py bdist --help-formats

1.3基本术语:

        模块(module):       Python中可复用的基本代码单元,可由其他代码import的一块代码,这里我们只关注三种类型的模块:纯python模块,扩展模块和包。

        纯python模块(pure Python module):      由python编写的模块,包含在单独的py文件中(或者是pyc/pyo文件)。

        扩展模块(extension module):由实现Python的底层语言编写的模块(C/C++ for Python, Java for Jython)。通常包含在单独的动态加载文件中,比如Unix中的so文件,windows中的DLL文件,或者是Jython扩展的java类文件。(注意,目前为止Distutils只能处理Python的C/C++扩展)

        包(package):包是含其他模块的模块,经常由包含__init__.py文件的目录发布。

        Root包(root package):       包层次关系中的根(它不是真正的包,因为它不包含__init__.py文件)。


1.4 Distutils术语

        模块发布(module distribution):一些Python模块的集合,它们将会被一起安装。一些常见的模块发布有Numeric Python,PyXML,PIL,mxBase。

        纯模块发布:一个只包含纯python模块和包的模块发布。

        非纯模块发布:至少包含一个扩展模块的模块发布。

        发布根:源码树的根目录;setup.py所在的目录。

二:编写setup脚本

       setup脚本是使用Distutils构建、发布和安装模块的核心。setup脚本的作用是向Distutils描述发布模块的信息。从上面那个简单的例子中可知,setup脚本主要是调用setup函数,而且模块开发者向Distutils提供的模块信息多数是由setup函数的关键字参数提供的。

       下面是一个更高级一些的例子:Distutils模块本身的setup脚本:

setup(name='Distutils',

      version='1.0',

      description='Python Distribution Utilities',

      author='Greg Ward',

      author_email='gward@python.net',

      url='https://www.python.org/sigs/distutils-sig/',

      packages=['distutils', 'distutils.command'],

    )

 上面这个脚本有更多的元数据,列出的是两个包(packages),而不是列出每个模块。因为Distutils包含多个模块,这些模块分成了两个包;如果列出所有模块的话则是冗长且难以维护的。

       注意,在setup脚本中的路径必须以Unix形式来书写,也就是由”/”分割的。Distutils会在使用这些路径之前,将这种表示方法转换为适合当前平台的格式。


2.1列出整个包

         Setup函数的packages参数是一个列表,其中包含了Distutils需要处理(构建、发布、安装等)的所有包。要实现此目的,那么包名和目录名必须能够相互对应,比如包名是distutils,则意味着在发布的根目录(setup脚本所在目录)下存在distutils子目录;再比如在setup脚本中packages = ['foo'],意味着要在setup脚本所在目录下存在相应的foo目录和foo/__init__.py文件。

       比如如果setup脚本内容如下:

setup(name='foo',

      version='1.0',

      packages = ['foo']

    )

而setup脚本所在目录并没有foo目录(只有一个setup.py脚本),则在执行python setup.py bdist命令时,会打印如下错误:

error: package directory 'foo' does not exist

        如果创建了foo目录,但是没有foo/__init__.py文件,则Distutils会产生下面的警告,但是仍会处理该包:

package init file 'foo/__init__.py' not found (or not a regular file)

  可以使用package_dir选项来改变这种默认的对应规则。package_dir是个字典,其中的key是要安装的包名,如果为空,则表明是root package,value就是该包(key)对应的源码树的目录。

        比如如果setup.py内容如下:

setup(name='foo',

      version='1.0',

      package_dir = {'':'lib'},

      packages = ['foo']

    )

    则必须在目录中存在lib子目录,lib/foo子目录,以及文件lib/foo/__init__.py。所以源码树如下:

setup.py

lib/

    foo/

        __init__.py

        foo.py

 最后生成的文件是:

\usr\local\lib\python2.7\dist-packages\ foo-1.0.egg-info

\usr\local\lib\python2.7\dist-packages\foo\__init__.py

\usr\local\lib\python2.7\dist-packages\foo\__init__.pyc

\usr\local\lib\python2.7\dist-packages\foo\foo.py

\usr\local\lib\python2.7\dist-packages\foo\foo.pyc


       另外一个例子,foo包对应lib目录,所以,foo.bar包就对应着lib/bar子目录。所以如果在setup.py中这么写:

     package_dir = {'foo':'lib'},

      packages = ['foo',’foo.bar’]

       则必须存在lib/__init__.py,  lib/bar/__init__.py文件。源码树如下:

setup.py

lib/

    __init__.py

    foo.py

    bar/

        __init__.py

        bar.py

       最后生成的文件是:

\usr\local\lib\python2.7\dist-packages\ foo-1.0.egg-info

\usr\local\lib\python2.7\dist-packages\foo\__init__.py

\usr\local\lib\python2.7\dist-packages\foo\__init__.pyc

\usr\local\lib\python2.7\dist-packages\foo\foo.py

\usr\local\lib\python2.7\dist-packages\foo\foo.pyc

\usr\local\lib\python2.7\dist-packages\foo\bar\__init__.py

\usr\local\lib\python2.7\dist-packages\foo\bar\__init__.pyc

\usr\local\lib\python2.7\dist-packages\foo\bar\bar.py

\usr\local\lib\python2.7\dist-packages\foo\bar\bar.pyc

2.2列出单独的模块

       如果发布中仅包含较少的模块,你可能更喜欢列出所有模块,而不是列出包,特别是在root package中存在单一模块的情况(或者根本就没有包)。可以使用py_modules参数,比如下面的例子:

setup(name='foo',

      version='1.0',

      py_modules = ['mod1', 'pkg.mod2']

    )

         它描述了两个模块,一个在root package中,另一个在pkg包中。根据默认的包/目录对应规则,这两个模块存在于文件mod1.py和pkg/mod2.py中,并且要存在pkg/__init__.py文件(不存在的话,会产生报警:package init file 'pkg/__init__.py' not found (or not a regular file))。当然,也可以使用package_dir选项改变这种对应关系。所以,源码树如下:

setup.py

mod1.py

pkg/

    __init__.py

    mod2.py

         最终生成的文件是:

\usr\local\lib\python2.7\dist-packages\foo-1.0.egg-info

\usr\local\lib\python2.7\dist-packages\mod1.py

\usr\local\lib\python2.7\dist-packages\mod1.pyc

\usr\local\lib\python2.7\dist-packages\pkg\__init__.py

\usr\local\lib\python2.7\dist-packages\pkg\__init__.pyc

\usr\local\lib\python2.7\dist-packages\pkg\mod2.py

\usr\local\lib\python2.7\dist-packages\pkg\mod2.pyc


2.3扩展模块

         在Distutils中描述扩展模块较描述纯python模块要复杂一些。对于纯python模块,仅需要列出模块或包,然后Distutils就会去寻找合适的文件,这对于扩展模块来说是不够的,你还需要指定扩展名、源码文件以及其他编译/链接需要的参数(需要包含的目录,需要连接的库等等)

        描述扩展模块可以由setup函数的关键字参数ext_modules实现。ext_modules是Extension实例的列表,每一个Extension实例描述了一个独立的扩展模块。比如发布中包含一个独立的扩展模块称为foo,由foo.c实现,且无需其他编译链接指令,那么下面的语句就可以描述该扩展模块:

Extension('foo', ['foo.c'])

         Extension可以从distutils.core中随setup一起引入。因此,对于仅包含一个扩展模块的发布来说,setup脚本如下:

from distutils.core import setup, Extension

setup(name='foo',

      version='1.0',

      ext_modules=[Extension('foo', ['foo.c'])],

      )

        底层的扩展构建机制是由build_ext命令实现的。Extension类在描述Python扩展时具有很大的灵活性。


2.3.1 扩展名和包

         通常,Extension类的构造函数的第一个参数都是扩展的名字,比如下面的语句:

Extension('foo', ['src/foo1.c', 'src/foo2.c'])

         如果执行python  setup.py bdist,就会调用相应的编译器和连接器命令,最终根据生成foo.so文件,存放在发布包的根目录中,最终生成的文件是:

\usr\local\lib\python2.7\dist-packages\foo.so

\usr\local\lib\python2.7\dist-packages\foo-1.0.egg-info


        又比如下面的语句:

Extension('pkg.foo', ['src/foo1.c', 'src/foo2.c'])

        使用的源文件是一样的,最终生成的结果文件也是一样的foo.so,唯一的不同是最终的结果文件存放的目录,是在发布包的根目录下的pkg目录下。因此最终生成的文件是:

\usr\local\lib\python2.7\dist-packages\pkg\foo.so

\usr\local\lib\python2.7\dist-packages\foo-1.0.egg-info


        如果一个包下有多个扩展,而且要把这些扩展都放在统一的目录下,则可以使用ext_package关键字,比如下面的语句:

setup(...,

      ext_package='pkg',

      ext_modules=[Extension('foo', ['src/foo.c']),

                  Extension('subpkg.bar', ['src/bar.c'])]

    )

         上面的描述将会编译src/foo.c为pkg.foo,将src/bar.c编译为pkg.subpkg.bar。因此源码树如下:

setup.py

src/

    foo.c

    bar.c

         最终生成的文件是:

\usr\local\lib\python2.7\dist-packages\foo-1.0.egg-info

\usr\local\lib\python2.7\dist-packages\pkg\foo.so

\usr\local\lib\python2.7\dist-packages\pkg\subpkg\bar.so


2.3.2 扩展的源码文件

         Extension构建函数的第二个参数是源文件的列表。目前Distutils仅支持C、C++和Objective-C扩展,所以这些源码文件就是C、C++和Objective-C的源码文件。(C++源码文件的扩展名可以是.cc和.cpp,Unix和Windows编译器都支持)

         不过还可以在列表中包含SWIG接口文件(.i文件),build_ext命令知道如何处理SWIG接口文件。尽管会发生报警,但是可以像下面这样传递SWIG选项:

setup(...,

      ext_modules=[Extension('_foo', ['foo.i'],

                            swig_opts=['-modern', '-I../include'])],

      py_modules=['foo'],

    )

         或者是使用如下命令:

> python setup.py build_ext --swig-opts="-modern -I../include"

         在一些系统上,该列表中还可以包含能由编译器处理的非源码文件。当前只支持Windows message 文本文件(.mc)和Visual C++的资源定义文件(.rc)。它们将会编译为二进制文件.res并且链接进可执行文件中。

2.3.3其他选项

         Extension还可以指定其他选项,比如可以指定头文件目录,define或undefine宏、需要链接的库,链接时和运行时搜索库的路径等等。具体可参阅:

https://docs.python.org/2/distutils/setupscript.html#preprocessor-options

https://docs.python.org/2/distutils/setupscript.html#library-options

https://docs.python.org/2/distutils/setupscript.html#other-options


2.4发布和包的关系

         发布和包有三种关系:它依赖其他包,它服务于其他包,它淘汰其他包。这些关系可以分别用setup函数的参数requires ,provides 和obsoletes 来指定,具体参阅:https://docs.python.org/2/distutils/setupscript.html#relationships-between-distributions-and-packages


2.5安装脚本

         模块通常不自己运行,而是由脚本引入。除了可以安装模块之外,还可以安装能直接运行的脚本,具体参阅https://docs.python.org/2/distutils/setupscript.html#installing-scripts


2.6安装package data

         有时包中还需要安装其他文件,这些文件与包的实现密切相关,或者是包含文档信息的文本文件等,这些文件就叫做package data。

         使用setup函数中的package_data参数可以向packages中添加package data。该参数的值必须是个字典,字典的key就是package name,value是个list,其中包含了需要复制到package中的一系列路径。这些路径都是相对于包目录而言的(比如package_dir),所以,这些文件必须存在于包的源码目录中。在安装时,也会创建相应的目录。

         比如,如果包中有一个包含数据文件的子目录,源码树如下:

setup.py

src/

    mypkg/

        __init__.py

        module.py

        data/

            tables.dat

            spoons.dat

            forks.dat

         相应的setup函数可以这样写:

setup(...,

      packages=['mypkg'],

      package_dir={'mypkg': 'src/mypkg'},

      package_data={'mypkg': ['data/*.dat']},

      )

2.7安装其他文件

         可以通过data_files选项来安装除了上面提到过的文件之外的其他文件,比如配置文件,数据文件等。data_files是个列表,列表中的元素是(directory, files),比如:

setup(...,

      data_files=[('bitmaps', ['bm/b1.gif', 'bm/b2.gif']),

                  ('config', ['cfg/data.cfg']),

                  ('/etc/init.d', ['init-script'])]

    )

         (directory, files)中,directory表示文件最终要被安装到的地方,如果它是相对路径的话,则是相对于installation prefix而言(对于纯python包而言,就是sys.prefix;对于扩展包,则是sys.exec_prefix)。files是要安装的文件,其中的目录信息(安装前)是相对于setup.py所在目录而言的,安装时,setup.py根据files的信息找到该文件,然后将其安装到directory中。


2.8元数据

         Setup脚本可以包含很多发布的元数据,比如名称、版本、作者等信息,具体列表和注意信息,参阅https://docs.python.org/2/distutils/setupscript.html#additional-meta-data


2.9调试setup脚本

         如果在运行setup脚本是发生了错误,则Distutils会打印出简单的错误信息,对于开发者而言这些错误信息可能不足以找到错误的原因。所以可以通过设置环境变量DISTUTILS_DEBUG,将其置为任意值(不能是空字符串),Distutils就会打印其执行过程的详细信息,并且在发生异常时打印全部的traceback,并且在像C编译器这样的外部程序发生错误时,打印整个命令行。

 

三:配置文件

         一般情况下,在构建发布时无法将所有的选项都确定下来,有些选项的值可能来自于用户,或者用户的系统。这也就是配置文件setup.cfg存在的目的,用户可以通过修改该配置文件进行选项的配置。

         在构建时,选项的处理顺序是setup脚本、配置文件,命令行。所以,安装者可以通过修改setup.cfg文件来覆盖setup.py中的选项;也可以通过运行setup.py时的命令行选项,来覆盖setup.cfg。


         配置文件的基本语法如下:

[command]

option=value

...

         command就是Distutils的命令(比如build_py,install等),option就是命令支持的选项。配置文件中的空行、注释(以’#’开头,直到行尾)会被忽略。


         可以通过--help选项得到某个命令支持的选项,比如:

> python setup.py --help build_ext

[...]

Options for 'build_ext' command:

  --build-lib (-b)    directory for compiled extension modules

  --build-temp (-t)    directory for temporary files (build by-products)

  --inplace (-i)      ignore build-lib and put compiled extensions into the

                      source directory alongside your pure Python modules

  --include-dirs (-I)  list of directories to search for header files

  --define (-D)        C preprocessor macros to define

  --undef (-U)        C preprocessor macros to undefine

  --swig-opts          list of SWIG command line options

[...]

        注意,命令行中的选项”--foo-bar”,在配置文件中要写成”foo_bar”。


        比如,运行以下命令:

python setup.py build_ext --inplace

        如果不希望每次执行命令时都输入”--inplace”选项,则可以在配置文件中写明:

[build_ext]

inplace=1

        其他例子和注意事项,可以参阅https://docs.python.org/2/distutils/configfile.html


四:源码发布

        之前已经提到过,使用sdist命令可以创建包的源码发布,该命令最终生成一个archive文件。Unix上默认的文件格式是.tar.gz,在Windows上的是ZIP文件。可以使用”--formats”选项指定生成的格式,比如:python setup.py sdist --formats=gztar,zip,执行该命令后,就会生成两个文件foo-1.0.tar.gz 和foo-1.0.zip。

        支持的格式有:

Format                                                         Description

zipzip                                                                 file (.zip)

gztar                                                          gzip’ed tar   file (.tar.gz)

bztar                                                             bzip2’ed tar file (.tar.bz2)

ztar                                                             compressed tar file (.tar.Z)

tar                                                             tar file (.tar)


    当在Unix上使用tar格式时(gztar,bztar,ztar或tar),可以通过owner和group选项指定用户和群组。比如:

python setup.py sdist --owner=root --group=root

4.1指定发布的文件

         如果没有明确的列出需要发布的文件,则sdist命令默认在源码发布中包含下列文件:

由py_modules和packages选项指定的所有python源码文件;

由ext_modules或libraries选项指定的所有C源码文件;

由scripts指定的脚本;

测试脚本:test/test*.py;

README.txt (或者README), setup.py 和setup.cfg;

package_data指定的所有文件;

data_files指定的所有文件。


        如果还需要发布其他额外的文件,典型的做法是编写一个叫做MANIFEST.in的manifest模板。manifest模板包含如何创建MANIFEST文件的一系列指令,sdist命令会解析该模板,根据模板中的指令,以及找到的文件生成MANIFEST。

        文件MANIFEST中明确的列出了包含在源码发布中的所有文件。比如下面就是一个MANIFEST文件的内容:

# file GENERATED by distutils, do NOT edit

setup.py

lib/__init__.py

lib/foo.py

lib/bar/__init__.py

lib/bar/bar.py

4.2 Manifest相关选项

        sdist命令的执行步骤如下:

        if the manifest file (MANIFEST by default) exists and the first line does not have a comment indicating it is generated from MANIFEST.in, then it is used as is, unaltered;

        if the manifest file doesn’t exist or has been previously automatically generated, read MANIFEST.in and create the manifest;

        if neither MANIFEST nor MANIFEST.in exist, create a manifest with just the default file set;

        use the list of files now in MANIFEST (either just generated or read in) to create the source distribution archive(s).

        如果仅仅需要(重新)创建MANIFEST文件,则可以使用如下命令:

python setup.py sdist --manifest-only

4.3 MANIFEST.in模板

         如果存在MANIFEST.in文件,则sdist命令就会根据该文件生成MANIFEST。在MANIFEST.in文件中,一行一个命令,每一个命令指定了源码发布中需要包含或者需要排除的文件,比如下面的例子:

include *.txt

recursive-include examples *.txt *.py

prune examples/sample?/build

         很容易看出,上面的命令的意思是:包含所有的.txt文件;包含examples目录下的所有.txt或者.py文件;排除所有匹配examples/sample?/build的目录。所有这些过程,都是在标准规则执行之后执行的,所以可以在模板文件中排除标准集合中的文件。关于MANIFEST文件的其他内容,参阅https://docs.python.org/2/distutils/sourcedist.html


五:构建发布(Built Distributions

         所谓的构建发布(built distribution),即是指二进制包,或是指安装文件。当然它未必真的是二进制,而有可能包含Python源码和字节码。

         构建发布是为了方便安装者而创建的,比如对于基于RPM的Linux用户来说,它可以是二进制RPM包,而对于Windows用户来说,它可以是一个可执行的安装文件等。

         创建包的构建发布,是前面介绍的packager的主要职责。它们拿到包的源码发布之后,使用setup脚本以及bdist命令来生成构建发布。比如,在包的源码树中运行下面的命令:

python setup.py bdist

         Distutils就会创建发布,执行“伪”安装(在build目录中),并且创建当前平台下的默认格式的构建发布。构建发布在Unix中的默认格式是一个”dumb”的tar文件(之所以称之为”dumb”,是因为该tar文件只有解压到特定的目录下才能工作),而在Windows上是一个简单可执行安装文件。

         所以,在Unix上运行上面的命令之后,就会在dist目录中生成foo-1.0.linux-i686.tar.gz文件,在合适的位置解压该文件,就安装了foo模块,等同于下载了该模块的源码发布之后运行python setup.py install命令。所谓合适的位置,要么是文件系统的根目录,要么是Python的prefix目录,这取决于bdist_dump的命令选项。

         bdist命令有一个--formats选项,类似于sdist命令,该选项可用于指定生成的构建发布的格式,比如命令:

python setup.py bdist --format=zip

         在Unix上运行该命令,就会创建foo-1.0.linux-i686.zip文件,在根目录下解压该文件就安装了foo模块。构建发布支持的格式如下:

gztar                                                                                                                   gzipped tar file (.tar.gz)

ztar                                                                                                                   compressed tar file (.tar.Z)

tar                                                                                                                   tar file (.tar)

zip                                                                                                                   zip file (.zip)

rpm                                                                                                                   RPM

pkgtool                                                                                                          Solaris pkgtool

sdux                                                                                                                   HP-UX swinstall

wininst                                                                                                       self-extracting ZIP file for Windows

msi                                                                                                                   Microsoft Installer.


        具体的子命令信息,可以参阅https://docs.python.org/2/distutils/builtdist.html


六:Distutils与PYPI

         PYPI,也就是Python Package Index,它是Python第三方模块的集中营,Python开发者可以向PYPI上传自己的Python模块。PYPI中存放了发布文件以及发布的元数据。

         Distutils提供了register和upload命令,来直接向PYPI推送元数据和发布文件,详细内容可以参阅https://docs.python.org/2/distutils/packageindex.html


七:简单示例

7.1纯Python发布(模块)

         如果只是发布几个模块,这些模块没有放在包中,可是使用py_modules选项。比如源码树如下:

setup.py

foo.py

bar.py

         setup脚本如下:

from distutils.core import setup

setup(name='foobar',

      version='1.0',

      py_modules=['foo', 'bar'],

      )

         安装之后,会生成以下文件:

\usr\lib\python2.7\site-packages\foobar-1.0-py2.7.egg-info

\usr\lib\python2.7\site-packages\ foo.py

\usr\lib\python2.7\site-packages\ foo.pyc

\usr\lib\python2.7\site-packages\ bar.py

\usr\lib\python2.7\site-packages\ bar.pyc


7.1纯Python发布(包)

         如果有很多模块需要发布,则可以将这些模块放到统一的包中,然后在setup脚本中指明要发布的包,而不是列出所有的模块。

         即使模块没有放到包中,也可以通过向setup脚本声明root包的方法来发布,与实际的包不同,根目录下可以没有__init__.py文件。比如上面的例子,源码树保持不变,setup脚本也可以这样写:

from distutils.core import setup

setup(name='foobar',

      version='1.0',

      packages=[''],

      )

         空字符串就意味着root包。安装之后,生成的文件跟上面是一样的。


         如果将源文件放到发布根目录下的子目录中,比如源码树:

setup.py

src/     

        foo.py

        bar.py

         这种情况依然可以用声明root包的方式来发布,只不过需要使用package_dir选项来指明包和目录的关系:

from distutils.core import setup

setup(name='foobar',

      version='1.0',

      package_dir={'': 'src'},

      packages=[''],

      )

         安装之后生成的文件跟之前是一样的。


         更常见的做法是将多个模块组织在同一个包中,比如在包foobar中包含foo和bar模块,源码树如下

setup.py

foobar/

        __init__.py

        foo.py

        bar.py

         setup脚本如下:

from distutils.core import setup

setup(name='foobar',

      version='1.0',

      packages=['foobar'],

      )

         安装之后,会生成以下文件:

\usr\lib\python2.7\site-packages\foobar-1.0-py2.7.egg-info

\usr\lib\python2.7\site-packages\foobar\ __init__.py

\usr\lib\python2.7\site-packages\foobar\ __init__.pyc

\usr\lib\python2.7\site-packages\foobar\foo.py

\usr\lib\python2.7\site-packages\foobar\foo.pyc

\usr\lib\python2.7\site-packages\foobar\bar.py

\usr\lib\python2.7\site-packages\foobar\bar.pyc


         如果不想以模块所在的子目录名来定义包名,则可以使用package_dir选项,比如源码树如下:

setup.py

src/

        __init__.py

        foo.py

        bar.py

         则相应的setup脚本如下:

from distutils.core import setup

setup(name='foobar',

      version='1.0',

      package_dir={'foobar': 'src'},

      packages=['foobar'],

      )

         安装之后生成的文件与上面的例子是一样的。


         或者,直接将所有模块放到发布的根目录下:

setup.py

__init__.py

foo.py

bar.py

         setup脚本如下:

from distutils.core import setup

setup(name='foobar',

      version='1.0',

      package_dir={'foobar': ''},

      packages=['foobar'],

      )

        安装之后生成的文件与上面的例子是一样的。


         如果涉及到子包的话,则必须在packages选项中明确的指出。不过,package_dir中的值却会自动扩展到其子目录。比如源码树如下:

setup.py

src/

__init__.py

        foo.py

        bar.py

        subfoo/

                __init__.py

                blah.py

         setup脚本如下:

from distutils.core import setup

setup(name='foobar',

      version='1.0',

      package_dir = {'foobar':'src'},

      packages=['foobar', 'foobar.subfoo'],

      )

         安装之后,生成文件如下:

\usr\lib\python2.7\site-packages\foobar-1.0-py2.7.egg-info

\usr\lib\python2.7\site-packages\foobar\ __init__.py

\usr\lib\python2.7\site-packages\foobar\ __init__.pyc

\usr\lib\python2.7\site-packages\foobar\foo.py

\usr\lib\python2.7\site-packages\foobar\foo.pyc

\usr\lib\python2.7\site-packages\foobar\bar.py

\usr\lib\python2.7\site-packages\foobar\bar.pyc

\usr\lib\python2.7\site-packages\foobar\subfoo\__init__.py

\usr\lib\python2.7\site-packages\foobar\subfoo\__init__.pyc

\usr\lib\python2.7\site-packages\foobar\subfoo\blah.py

\usr\lib\python2.7\site-packages\foobar\subfoo\blah.pyc


7.3单独的扩展模块

         扩展模块由选项ext_modules指定。package_dir选项对扩展模块的源码文件没有作用,它只影响纯Python模块。比如源码树如下:

setup.py

foo.c

         如果setup脚本如下:

from distutils.core import setup

from distutils.extension import Extension

setup(name='foobar',

      version='1.0',

      ext_modules=[Extension('foo', ['foo.c'])],

      )

         则生成的文件是:

\usr\lib\python2.7\site-packages\foobar-1.0-py2.7.egg-info

\usr\lib\python2.7\site-packages\ foo.so


         如果源码树不变,setup脚本如下:

from distutils.core import setup

from distutils.extension import Extension

setup(name='foobar',

      version='1.0',

      ext_modules=[Extension('foopkg.foo', ['foo.c'])],

      )

         则生成的文件是:

\usr\lib\python2.7\site-packages\foobar-1.0-py2.7.egg-info

\usr\lib\python2.7\site-packages\foopkg\ foo.so


八:其他

         运行install命令,会首先运行build命令,然后运行子命令install_lib,install_data和install_scripts。


         Distutils可以进行扩展,比如增加新的命令、修改现有的命令。可参阅https://docs.python.org/2/distutils/extending.html


         Distutils的API参阅https://docs.python.org/2/distutils/apiref.html


原文参考:https://blog.csdn.net/gqtcgq/article/details/49255995

推荐阅读更多精彩内容