Python深入:Distutils发布Python模块--转载

时间:2021-06-14 19:00:57

https://blog.csdn.net/gqtcgq/article/details/49255995

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脚本可以这样写:

  1. from distutils.core import setup
  2. setup(name='foo',
  3. version='1.0',
  4. py_modules=['foo'],
  5. )

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

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

  1. python setup.py sdist

对于Windows而言,命令是:

  1. 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目录,然后运行:

  1. python setup.py install

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

  1. # python setup.py install
  2. running install
  3. running build
  4. running build_py
  5. creating build
  6. creating build/lib
  7. copying foo.py -> build/lib
  8. running install_lib
  9. copying build/lib/foo.py -> /usr/lib/python2.7/site-packages
  10. byte-compiling /usr/lib/python2.7/site-packages/foo.py to foo.pyc
  11. running install_egg_info
  12. 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文件:

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

  1. python setup.py bdist_rpm

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

  1. 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脚本:

  1. setup(name='Distutils',
  2. version='1.0',
  3. description='Python Distribution Utilities',
  4. author='Greg Ward',
  5. author_email='gward@python.net',
  6. url='https://www.python.org/sigs/distutils-sig/',
  7. packages=['distutils', 'distutils.command'],
  8. )

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

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

2.1列出整个包

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

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

  1. setup(name='foo',
  2. version='1.0',
  3. packages = ['foo']
  4. )

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

  1. error: package directory 'foo' does not exist

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

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

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

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

  1. setup(name='foo',
  2. version='1.0',
  3. package_dir = {'':'lib'},
  4. packages = ['foo']
  5. )

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

  1. setup.py
  2. lib/
  3. foo/
  4. __init__.py
  5. 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中这么写:

  1. package_dir = {'foo':'lib'},
  2. packages = ['foo',’foo.bar’]

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

  1. setup.py
  2. lib/
  3. __init__.py
  4. foo.py
  5. bar/
  6. __init__.py
  7. 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参数,比如下面的例子:

  1. setup(name='foo',
  2. version='1.0',
  3. py_modules = ['mod1', 'pkg.mod2']
  4. )

它描述了两个模块,一个在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选项改变这种对应关系。所以,源码树如下:

  1. setup.py
  2. mod1.py
  3. pkg/
  4. __init__.py
  5. 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实现,且无需其他编译链接指令,那么下面的语句就可以描述该扩展模块:

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

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

  1. from distutils.core import setup, Extension
  2. setup(name='foo',
  3. version='1.0',
  4. ext_modules=[Extension('foo', ['foo.c'])],
  5. )

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

2.3.1 扩展名和包

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

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

又比如下面的语句:

  1. 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关键字,比如下面的语句:

  1. setup(...,
  2. ext_package='pkg',
  3. ext_modules=[Extension('foo', ['src/foo.c']),
  4. Extension('subpkg.bar', ['src/bar.c'])]
  5. )

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

  1. setup.py
  2. src/
  3. foo.c
  4. 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选项:

  1. setup(...,
  2. ext_modules=[Extension('_foo', ['foo.i'],
  3. swig_opts=['-modern', '-I../include'])],
  4. py_modules=['foo'],
  5. )

或者是使用如下命令:

  1. > 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),所以,这些文件必须存在于包的源码目录中。在安装时,也会创建相应的目录。

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

  1. setup.py
  2. src/
  3. mypkg/
  4. __init__.py
  5. module.py
  6. data/
  7. tables.dat
  8. spoons.dat
  9. forks.dat

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

  1. setup(...,
  2. packages=['mypkg'],
  3. package_dir={'mypkg': 'src/mypkg'},
  4. package_data={'mypkg': ['data/*.dat']},
  5. )

2.7安装其他文件

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

  1. setup(...,
  2. data_files=[('bitmaps', ['bm/b1.gif', 'bm/b2.gif']),
  3. ('config', ['cfg/data.cfg']),
  4. ('/etc/init.d', ['init-script'])]
  5. )

(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。

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

  1. [command]
  2. option=value
  3. ...

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

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

  1. > python setup.py --help build_ext
  2. [...]
  3. Options for 'build_ext' command:
  4. --build-lib (-b)     directory for compiled extension modules
  5. --build-temp (-t)    directory for temporary files (build by-products)
  6. --inplace (-i)       ignore build-lib and put compiled extensions into the
  7. source directory alongside your pure Python modules
  8. --include-dirs (-I)  list of directories to search for header files
  9. --define (-D)        C preprocessor macros to define
  10. --undef (-U)         C preprocessor macros to undefine
  11. --swig-opts          list of SWIG command line options
  12. [...]

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

比如,运行以下命令:

  1. python setup.py build_ext --inplace

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

  1. [build_ext]
  2. 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

zip

zip 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选项指定用户和群组。比如:

  1. 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文件的内容:

  1. # file GENERATED by distutils, do NOT edit
  2. setup.py
  3. lib/__init__.py
  4. lib/foo.py
  5. lib/bar/__init__.py
  6. 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文件,则可以使用如下命令:

  1. python setup.py sdist --manifest-only

4.3 MANIFEST.in模板

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

  1. include *.txt
  2. recursive-include examples *.txt *.py
  3. 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命令来生成构建发布。比如,在包的源码树中运行下面的命令:

  1. 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命令,该选项可用于指定生成的构建发布的格式,比如命令:

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

当然,也可以不使用--formats选项,而是用bdist的子命令,直接创建相应的格式。比如使用bdist_dump命令可以生成所有的dumb archive格式(tar,ztar,gztar和zip),bdist_rpm会生成源码和二进制的RPM包,bdist的子命令如下表:

Command

Formats

bdist_dumb

tar, ztar, gztar, zip

bdist_rpm

rpm, srpm

bdist_wininst

wininst

bdist_msi

msi

具体的子命令信息,可以参阅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选项。比如源码树如下:

  1. setup.py
  2. foo.py
  3. bar.py

setup脚本如下:

  1. from distutils.core import setup
  2. setup(name='foobar',
  3. version='1.0',
  4. py_modules=['foo', 'bar'],
  5. )

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

\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脚本也可以这样写:

  1. from distutils.core import setup
  2. setup(name='foobar',
  3. version='1.0',
  4. packages=[''],
  5. )

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

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

  1. setup.py
  2. src/
  3. foo.py
  4. bar.py

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

  1. from distutils.core import setup
  2. setup(name='foobar',
  3. version='1.0',
  4. package_dir={'': 'src'},
  5. packages=[''],
  6. )

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

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

  1. setup.py
  2. foobar/
  3. __init__.py
  4. foo.py
  5. bar.py

setup脚本如下:

  1. from distutils.core import setup
  2. setup(name='foobar',
  3. version='1.0',
  4. packages=['foobar'],
  5. )

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

\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选项,比如源码树如下:

  1. setup.py
  2. src/
  3. __init__.py
  4. foo.py
  5. bar.py

则相应的setup脚本如下:

  1. from distutils.core import setup
  2. setup(name='foobar',
  3. version='1.0',
  4. package_dir={'foobar': 'src'},
  5. packages=['foobar'],
  6. )

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

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

  1. setup.py
  2. __init__.py
  3. foo.py
  4. bar.py

setup脚本如下:

  1. from distutils.core import setup
  2. setup(name='foobar',
  3. version='1.0',
  4. package_dir={'foobar': ''},
  5. packages=['foobar'],
  6. )

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

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

  1. setup.py
  2. src/
  3. __init__.py
  4. foo.py
  5. bar.py
  6. subfoo/
  7. __init__.py
  8. blah.py

setup脚本如下:

  1. from distutils.core import setup
  2. setup(name='foobar',
  3. version='1.0',
  4. package_dir = {'foobar':'src'},
  5. packages=['foobar', 'foobar.subfoo'],
  6. )

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

\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模块。比如源码树如下:

  1. setup.py
  2. foo.c

如果setup脚本如下:

  1. from distutils.core import setup
  2. from distutils.extension import Extension
  3. setup(name='foobar',
  4. version='1.0',
  5. ext_modules=[Extension('foo', ['foo.c'])],
  6. )

则生成的文件是:

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

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

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

  1. from distutils.core import setup
  2. from distutils.extension import Extension
  3. setup(name='foobar',
  4. version='1.0',
  5. ext_modules=[Extension('foopkg.foo', ['foo.c'])],
  6. )

则生成的文件是:

\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