我们经常会使用pip setup.py install 安装一个源码包,可是这个可以安装的源码包是怎么生成的,这个setup.py又是怎么生成的呢?这篇文章中,我们就来一探究竟。
用来进行python程序打包的两个常用工具为Distutils和setuptools,据说setuptools是Distutils的高级版本,添加了更多特性。我们就先来看一看Distutils的使用,然后再看setuptools,添加了什么样的内容。
Distutils可以用来在Python环境中构建和安装额外(第三方)的模块。新的模块可以是纯Python的,也可以是用C/C++写的扩展模块,或者可以是Python包,包中包含了由C和Python编写的模块。
首先来明确几个基本的术语:
模块(module): Python中可复用的基本代码单元,可由其他代码import的一块代码,这里我们只关注三种类型的模块:纯python模块,扩展模块和包。
纯python模块(pure Python module): 由python编写的模块,包含在单独的py文件中(或者是pyc/pyo文件)。也就是一个python文件。
扩展模块(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文件)。
模块发布(module distribution): 一些Python模块的集合,它们将会被一起安装。一些常见的模块发布有Numeric Python,PyXML,PIL,mxBase。
纯模块发布: 一个只包含纯python模块和包的模块发布。
非纯模块发布: 至少包含一个扩展模块的模块发布。
发布根: 源码树的根目录;setup.py所在的目录。
对于模块开发者以及需要安装模块的使用者来说,Distutils的使用都很简单,作为一个开发者,除了编写源码之外,还需要:
有些模块开发者在开发时不会考虑多个平台发布,所以就有了packagers的角色,它们从模块开发者那取得源码发布,然后在多个平台上面进行构建,并发布多个平台的构建版本。
由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
该命令生成的文件是:
/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
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
这个简单的例子展示了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
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='[email protected]',
url='https://www.python.org/sigs/distutils-sig/',
packages=['distutils', 'distutils.command'],
)
上面这个脚本有更多的元数据,列出的是两个包(packages),而不是列出每个模块。因为Distutils包含多个模块,这些模块分成了两个包;如果列出所有模块的话则是冗长且难以维护的。
注意,在setup脚本中的路径必须以Unix形式来书写,也就是由”/”分割的。Distutils会在使用这些路径之前,将这种表示方法转换为适合当前平台的格式。
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
需要说明的这里的package_dir 表示的package和directory的对应关系,package表示安装后的python包名,directory对应python源码的目录。
- package_dir = {’’:‘lib’}的意思是将安装后的python包名对应到源码目录的lib目录。
- packages = [‘foo’] 表示需要打包的python包或者模块,也是生成的包或者模块的名字。
最后生成的文件是:
\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
这个例子稍微难理解一些,需要说明的这里的package_dir 表示的package和directory的对应关系,package表示安装后的python包名,directory对应python源码的目录。
- package_dir = {‘foo’:‘lib’},可以理解为安装后生成foo包,对应的源码目录为lib。
- packages = [‘foo’,’foo.bar’] 表示需要打包的python包或者模块。也是生成的包或者模块的名字。
最后生成的文件是:
\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
如果发布中仅包含较少的模块,你可能更喜欢列出所有模块,而不是列出包,特别是在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
在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扩展时具有很大的灵活性。
通常,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
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并且链接进可执行文件中。
发布和包有三种关系:它依赖其他包,它服务于其他包,它淘汰其他包。这些关系可以分别用setup函数的参数requires ,provides 和obsoletes 来指定,具体参阅:https://docs.python.org/2/distutils/setupscript.html#relationships-between-distributions-and-packages
模块通常不自己运行,而是由脚本引入。除了可以安装模块之外,还可以安装能直接运行的脚本,具体参阅https://docs.python.org/2/distutils/setupscript.html#installing-scripts
有时包中还需要安装其他文件,这些文件与包的实现密切相关,或者是包含文档信息的文本文件等,这些文件就叫做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']},
)
可以通过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中。
Setup脚本可以包含很多发布的元数据,比如名称、版本、作者等信息,具体列表和注意信息,参阅https://docs.python.org/2/distutils/setupscript.html#additional-meta-data
如果在运行setup脚本是发生了错误,则Distutils会打印出简单的错误信息,对于开发者而言这些错误信息可能不足以找到错误的原因。所以可以通过设置环境变量DISTUTILS_DEBUG,将其置为任意值(不能是空字符串),Distutils就会打印其执行过程的详细信息,并且在发生异常时打印全部的traceback,并且在像C编译器这样的外部程序发生错误时,打印整个命令行。
————————————————
版权声明:本文为CSDN博主「gqtcgq」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/gqtcgq/article/details/49255995
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
————————————————
版权声明:本文为CSDN博主「gqtcgq」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/gqtcgq/article/details/49255995
一般情况下,在构建发布时无法将所有的选项都确定下来,有些选项的值可能来自于用户,或者用户的系统。这也就是配置文件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。
支持的格式有:
当在Unix上使用tar格式时(gztar,bztar,ztar或tar),可以通过owner和group选项指定用户和群组。比如:
python setup.py sdist --owner=root --group=root
如果没有明确的列出需要发布的文件,则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
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
如果存在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 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模块。构建发布支持的格式如下:
当然,也可以不使用–formats选项,而是用bdist的子命令,直接创建相应的格式。比如使用bdist_dump命令可以生成所有的dumb archive格式(tar,ztar,gztar和zip),bdist_rpm会生成源码和二进制的RPM包,bdist的子命令如下表:
具体的子命令信息,可以参阅https://docs.python.org/2/distutils/builtdist.html
PYPI,也就是Python Package Index,它是Python第三方模块的集中营,Python开发者可以向PYPI上传自己的Python模块。PYPI中存放了发布文件以及发布的元数据。
Distutils提供了register和upload命令,来直接向PYPI推送元数据和发布文件,详细内容可以参阅https://docs.python.org/2/distutils/packageindex.html
如果只是发布几个模块,这些模块没有放在包中,可是使用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
如果有很多模块需要发布,则可以将这些模块放到统一的包中,然后在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
扩展模块由选项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
setuptools是distutils的增强版。setuptools有一个entry_points功能很方便,类似linux启动某个服务,如在linux命令行里firefox能启动火狐浏览器。
首先检验没有安装之前,命令path没有作用。
创建一个文件夹demo,在文件夹里创建get_path.py和__init__.py两个文件。get_path.py是功能函数,init.py是包的标识文件。
get_path.py
import os
def fun():
print "i am in the path:"
print os.getcwd()
创建setup.py文件,填写必要的打包信息。
setup.py
#-*- encoding: UTF-8 -*-
from setuptools import setup
setup(
name = "demo", # 包名
version = "0.1", # 版本信息
packages = ['demo'], # 要打包的项目文件夹
include_package_data=True, # 自动打包文件夹内所有数据
zip_safe=True, # 设定项目包为安全,不用每次都检测其安全性
install_requires = [ # 安装依赖的其他包(测试数据)
'docutils>=0.3',
'requests',
],
# 设置程序的入口为path
# 安装后,命令行执行path相当于调用get_path.py中的fun方法
entry_points={
'console_scripts':[
'path = demo.get_path:fun'
]
},
)
在配置中将该模块需要的依赖全部都写好,安装时指定地址去下载。这种方式简化了使用时的安装过程,但是还不够好。最好的方式是pip的自动下载。
python setup.py sdist
打包之后多出两个文件夹,分别是demo.egg-info和dist。demo.egg-info是必要的安装信息,而dist中的压缩包就是安装包。
查看dist/demo-0.1.tar.gz解压之后的文件。
python setup.py install --record log
安装之后在命令行中直接输入path,回车能够看到调用了get_path.py中的函数fun(),输出字符串。
同时也可以导入使用。
windows : for /F %i in (log) do del %i
linux : cat log | xagrs rm -rf
原文链接:
- Python深入:Distutils发布Python模块
- python打包工具distutils、setuptools分析