目录
setuptools和setup.py
你所需要做的事&一些概念
基础概念 关于源码分发文件和二进制分发文件示例和分发选择
purepythonmodule package extensionmodulepackage元信息参数
package内容参数
py_modules列举每个模块
package列举每个包
package_dir重新映射package和目录的关系
install_requires和dependency_links安装依赖模块
ext_modulePython调用C/C++
name扩展模块名字 sources和include_dirs define_macros和undef_macros libraries和library_dirs extra_compile_args创建源码分发
sdist命令 MANIFEST.in模版文件Setuptools和distutils都是用于编译、分发和安装python包的一个工具,特别是在包依赖问题场景下非常有用,它是一个强大的包管理工具。Setuptools是distutils的加强版。编译、分发和安装python包的一个关键的事就是编写setup脚本。setup脚本的主要作用在于向包管理工具Setuptools或distutils说明你的模块分发细节,所以Setuptools支持大量的命令以操作你的包。setup脚本主要调用一个setup()方法,许多提供给Setuptools的信息都以keywordarguments的参数形式提供给setup()方法。
对于包开发者和使用者,所需要做的事:
编写setup.py脚本,用于处理你的包 (可选)编写setup配置文件 创建源码分发文件,pythonsetup.pysdist, (可选)创建二进制分发文件,pythonsetup.pybdist对于包使用者,只需要pythonsetup.pyinstall,便可以成功安装python包。
源码分发文件是将包分享给其他人更为推荐的一种形式,因为源码分发文件比二进制分发文件更适合跨平台,这样使用者可以在自己的机器上通过编译得到自己的机器相关的包代码并且进行安装。
举个简单的例子,你需要发布两个模块foo以及bar.bar,以供别人使用(importfoo和importbar.bar)。
其目录树如下: pure_module ├──bar │└──bar.py │└──__init__.py ├──foo.py └──setup.py如上图所示,pure_module目录下包含了一个foo模块以及一个bar包,同时在bar包下还包含了一个bar模块。
一个仅使用py_modules的setup脚本可以这样写:
fromsetuptoolsimportsetup NAME=foo VERSION=1.0 PY_MODULES=[foo,bar.bar] setup(name=NAME ,version=VERSION ,py_modules=PY_MODULES)py_modules指定了foo模块以及bar.bar模块。
通过pythonsetup.pyinstall--user--prefix=进行安装后,便可以直接通过importfoo和importbar.bar直接使用了。
注意:经测试,如果.py文件位于其他文件夹,该文件夹需要创建一个允许为空的__init__.py文件,表示为一个package,否则安装后不能正常使用其他文件夹的模块。上一节的例子中,bar.bar属于bar包,foo位于distributionroot,安装后属于rootpackage,在Setuptools中""可以用于表示rootpackage。所以下面展示两种setup脚本的写法:
pure_module ├──bar │└──bar.py │└──__init__.py ├──foo.py └──setup.py仅使用packages的setup.py文件如下:
fromsetuptoolsimportsetup NAME=foo VERSION=1.0 PACKAGES=[,bar] setup(name=NAME ,version=VERSION ,packages=PACKAGES )如上,packages包含了package的列表rootpackage以及bar,这样便能轻松覆盖到distributionroot下的foo.py和bar文件夹下的bar.py了,在存在大量模块的情况下,省去像py_modules一样穷举模块的麻烦。在python中,默认的情况下,包的名字和目录的名字是一致的,比如bar包对应了bar目录,且包的路径表示是相对于distributionroot的(也就是setup.py所在目录)。比如packages=[foo],Setuptools会在setup.py所在目录下寻找foo/__init__.py,并将foo/下的所有模块包含进去。
另外一个关键字是package_dir,它的作用是将package映射到其他目录,这样的一个好处是方便将package移到其他目录而不用修改packages的参数值。举个例子,假设我们现在需要把bar移到foobar目录下,按照原来的脚本,Setuptools是无法成功找到bar包的。
package_dir ├──foobar │├──bar.py │└──__init__.py ├──foo.py └──setup.py通过package_dir=={bar:foobar},将原来的barpackage映射到foobar下。完整的脚本如下:
fromsetuptoolsimportsetup NAME=foo VERSION=1.0 PACKAGE_DIR={bar:foobar} PACKAGES=[,bar] setup(name=NAME ,version=VERSION ,package_dir=PACKAGE_DIR ,packages=PACKAGES )package_dir是一个字典,它的key是package名(""表示rootpackage),value是相对于distributionroot的目录名。在上面的例子中,package_dir=={bar:foobar}改变了packages中package对应的目录位置,这样当Setuptools在找barpackage时,会在foobar目录下找相应的__init__.py文件。
注意,package_dir会影响packages下列出的所有package,比如,packages=[bar,bar.lib],package_dir不仅会影响所有和bar有关的package,bar.lib也会相应被映射到foorbar.lib。扩展模块需要使用ext_modules参数。上面所说的package_dir和packages都是针对纯python模块,而ext_modules针对的是使用C/C++底层语言所写的模块。下面举个最简单的例子,扩展模块仅包含一个foo.cpp文件,其中定义了可供python调用的myPrint函数。
#include<iostream> #include<string> usingnamespacestd; voidmyPrint(stringtext) { cout<<text<<endl; } . ├──setup.py └──src ├──foo.cpp ├──foo.h └──PythonWrapAPI.cpp现在,我们想要发布扩展模块供别人使用我们的myPrint方法。想要python中成功导入你的包,需要利用额外的代码封装将被调用的方法。这里的PythonWrapAPI.cpp的作用就是使用Python提供的库封装你所写的接口,它是处在python和C++间的胶水库,当python调用你的C++方法时,由于语言类型的差别,需要做转换。
PythonWrapAPI.cpp如下:
#include"foo.h" #include<string> #include<python2.6/Python.h> usingnamespacestd; /* Notice:PythonInterfaceWrap */ staticPyObject*_myPrint(PyObject*self,PyObject*args) { char*text; //解析Python传过来的参数 if(!PyArg_ParseTuple(args,"s",&text)) returnNULL; myPrint(text); returnPy_None; } staticPyMethodDefExtestMethods[]= { {"myPrint",_myPrint,METH_VARARGS}, {NULL,NULL}, }; PyMODINIT_FUNCinitmyprint(void){ (void)Py_InitModule("myprint",ExtestMethods); } wrapper函数_myPrint。它负责将Python的参数转化为C/C++的参数(PyArg_ParseTuple),然后调用实际的myPrint,并处理myPrint的返回值,最终返回给Python环境。需要注意的是,C/C++中无返回值时,不能直接返回NULL,而是需要返回Py_None。 导出表ExtestMethods。它负责告诉Python这个模块里有哪些函数可以被Python调用。导出表的名字可以随便起,每一项有4个参数:第一个参数是提供给Python环境的函数名称,第二个参数是_myPrint,即wrapper函数。第三个参数的含义是参数变长,第四个参数是一个说明性的字符串。导出表总是以{NULL,NULL,0,NULL}结束。说明,第3和4个参数可以省略。 导出函数initmyprint。这个的名字不是任取的,是你的module名称添加前缀init。导出函数中将模块名称与导出表进行连接。扩展模块和纯python模块有点不太一样,我们导入的package名字与setup()的packages或者package_dir参数是一致的,但是扩展模块的名字是由Extension实例的name参数决定的,且需要和导出函数对应的initxxx名字以及Py_InitModule方法对应的第一个参数相同。
最后,我们需要编写setup脚本编译我们的cpp文件为so动态链接库,并进行相应的封装。运行pythonsetup.pyinstall,setuptools会帮我们自动编译。
fromsetuptoolsimportsetup,Extension,find_packages #packagename,importNAME NAME="foo" VERSION=1.0.0 #anExtensioninstancelist EXT_MODULES=[ Extension( name=myprint ,sources=[src/foo.cpp,src/PythonWrapAPI.cpp] ,include_dirs=[src] ) ] setup(name=NAME ,version=VERSION ,ext_modules=EXT_MODULES ,)ext_modules是一个Extension实例列表,Extension的参数sources用于指定所有源文件位置,include_dirs指定头文件位置,同时还可以使用library_dirs和libraries指定外部链接库,以及extra_compile_args指定额外的编译参数。
在编写一个package的时候,尽量提供更多的元信息,这样使用者更加能够了解到package的相关信息,并且有些信息会被PyPi使用。
元信息 类型 描述 说明 name shortstring package名字,这里用于pypi的显示,而不是用于import,import的包名与packages和package_dir参数一致 1 version shortstring package的发布版本,建议:major.minor[.patch[.sub]] 1 author shortstring package作者名字 3 author_email shortstring package作者邮箱 3 maintainer shortstring package维护者名字 3 maintainer_email shortstring package维护者邮箱 3 url shortstring package项目地址 1 description shortstring 简介 long_description longstring 显示于pypi的介绍 download_url shortstring package下载地址 2 classifiers stringslist 分类符,这样便于pypi索引,由pypi固定提供,https://pypi.python.org/pypi?%3Aaction=list_classifiers 2 platforms stringslist 支持的平台列表 license shortstring 授权协议 必填 如果为了兼容2.2.3或2.3版本,不建议使用此字段 如果提供了maintainer,那么distutils会将其加入PKG-INFOpy_modules是一个字符串列表,用于指定所有的模块,即py文件模块。如果你只是发布几个脚本文件而已,特别是它们逻辑上不属于同一个package。
比如,py_modules=[mod1,pkg.mod2]。这指定了两个模块,一个位于rootpackage,而另一个位于pkgpackage。如果没有使用package_dir重新映射package和目录的关系的话,那么这两个模块分别对应了mod1.py以及pkg/mod2.py文件,并且在pkg文件夹下还存在__init__.py文件。
如果你需要发布的模块文件太多,使用py_modules一个一个指定比较麻烦,特别是模块位于多个包中,那么你可以使用packages指定整个包。
packages是一个包名列表,packages参数告诉Setuptools处理列举出的package下所有纯python模块。在文件系统中,默认地,package的名字与目录是一一对应的,也就是说,packages=[foo],Setuptools会去查找foo/__init__.py文件。
当你想要重命名你的package所在的文件夹,或者想要移动整个package到其他目录下,一般情况下,一旦你的源代码布局改变,你需要重新修改packages。但是package_dir可以重新映射package和目录的关系。比如你将rootpackage下的模块和package移到lib目录下,那么你只需要在package_dir中将rootpackage映射到lib下。比如package_dir={:lib}
再举个例子,比如一下目录结果,当我使用package_dir={bar:foobar}和packages=[bar]时,Setuptools根据packages参数查找barpackage时,会在foobar文件夹下找相应的__init__.py文件。
package_dir ├──foobar │├──bar.py │└──__init__.py └──setup.py 注意,package_dir会影响packages下列出的所有package,比如,packages=[bar,bar.lib],package_dir不仅会影响所有和bar有关的package,bar.lib也会相应被映射到foorbar.lib。install_requires可以声明package安装时所需的依赖模块及其版本。安装package时,Setuptools便能够从PyPi上自动下载其所依赖的模块,并且将依赖信息包含进PythonEggs中。
比如我们在自己的package中用到了一个python非标准库pycurl和xmltodict,当我们的package在别的机器上使用时便会报错。为了解决这个问题,我们可以使用install_requires=[pycurl,xmltodict]将pycurl和xmltodict加入package依赖。
install_requires可以是string或stringlist,指明所需要依赖的模块。当install_requires为string类型,并且依赖多于1个时,每个依赖的声明都要另起一行。
最新版本的Setuptools的install_requires有另外两个作用:
在运行时,任何脚本都会检查其依赖模块的正确性,并且确保正确的依赖版本都加入到sys.path中(假如有多个版本的话)。 PythonEggdistributions将会包含依赖相关的元信息。前面说到,Setuptools便能够从PyPi上自动下载其所依赖的模块,但是在某些环境下无法正常访问Pypi下,我们也可以通过dependency_links参数指定到自己的python源,这样便可以解决下载问题。
比如,dependency_links=[http://xxx/xmltodict,http://xxx/pycurl]。
dependency_links是一个字符串列表,包含了依赖的下载URL。
Setuptools对链接的支持比较强大!
下载的资源可以满足以下条件:
通过pythonsetup.pysdist进行分发的压缩文件,默认情况下在linux为.tar.gz,在windows为zip 单一的py文件 VCS仓库(Subversion,Mercurial,Git)URL链接可以是:
可以直接下载的URL 包含资源下载链接的网页URL 仓库URL当包含资源下载链接的网页URL中存在多个版本时,Setuptools会根据版本要求下载合适的版本。
一般,比较好的方式是网页URL方式。我们也可以使用SourceForge的showfiles.php链接来下载我们所依赖的模块。
如果依赖的模块是一个py文件时,你必须在URL添加"#egg=project-version"后缀,以指出模块的名字和版本,另外需要确保将模块名和版本中出现的-替换为_。EasyInstall将会识别这个后缀并且自动创建一个setup.py脚本,将我们的py文件包装为egg文件。如果为VCS,将会checkout对应的版本,创建一个临时文件夹并执行setup.pybdist_egg,安装所需的依赖。
在使用VCS的情况下,你也可以使用#egg=project-version指定要使用的版本。你可以通过在#egg=project-version前加入@REV来指定要checkout的版本。另外你也可以通过在URL前加上以下标识显式声明URL使用的是VCS:
Subversion:svn+URL Git:git+URL Mercurial:hg+URL因此使用VCS更复杂的一个示例为:vcs+proto://host/path@revision#egg=project-version
Python的可扩展性特别强,不仅支持python语言的扩展模块,而且支持其他语言的扩展。
Python调用C++的详细文档可以查看https://docs.python.org/2/extending/building.html
这里假设已经懂得怎么调用C++方法了,接下来只需要使用ext_module参数,使Setuptools能够编译和安装扩展模块了。
ext_module参数是一个Extension实例列表,Extension类似于gcc/g++的所需参数,包含了指定源文件、头文件、依赖的静态库或动态库、额外的编译参数、宏定义等功能。
name是一个字符串,用于指定扩展模块的名字。
packages和package_dir用于支持python语言编写模块,其import语句使用的包名与packages和package_dir中所指定的名字是一致的。但是扩展模块的名字是由Extension实例的name参数决定的,且需要和导出函数对应的initxxx名字以及Py_InitModule方法对应的第一个参数相同。定义好模块的名字xxx后,我们便可以使用importxxx使用我们自己的模块了。
sources为用于指定要编译源文件的字符串列表,比如,sources=[foo/foo.cpp,bar/bar.cpp],Setuptools支持C/C++以及Objective-C。
include_dirs为用于指定编译需要的头文件目录的字符串列表,比如,include_dirs=[foo/include,bar/include]。如果头文件位于distributionroot目录,需要使用.表示头文件位于当前目录,不能为,否则将找不到头文件。另外还支持extra_objects向链接程序传递object文件,比如.o文件。
gcc支持在编译的时候定义新的宏变量和取消某个宏变量的定义,具体的选项[-Dmacro[=defn]...][-Umacro]。Extension也支持这样的选项。
你可以使用define_macros和undef_macros定义新的宏变量和取消某个宏变量的定义。
define_macros是一个(name,value)元组列表,其中的name为宏变量名字符串,value为对应的值,可以为字符串、数字或为None类型(说明:官方文档没有声明value可以为数字,但是经过测试,只要是python支持的数字类型都可以用于value,但是最好还是使用字符串的形式,这样脚本的兼容性会更好).
比如,define_macros=[(DEBUG,None),(FOO,1),(BAR,2),(FOOBAR,"abc")],gcc对应的编译选项结果为-DDEBUG-DFOO=1-DBAR=2-DFOOBAR="abc"。
undef_macros比define_macros简单得多,它就是一个宏变量字符串列表,举个例子,我们想要取消以上定义的宏变量,对应的undef_macros值为undef_macros=[DEBUG,FOO,BAR,FOOBAR]。
Setuptools对C/C++库的引用方法和gcc一样,具体的规则可以参考gcc。
libraries为要添加的库的名字字符串列表,而library_dirs为要添加的库所在的目录,举个例子:
. ├──setup.py └──curl ├──include ├──curl.h ├──test.h ├──lib ├──libcurl.a ├──libtest.a其对应的参数为libraries=[curl,test]、library_dirs=[curl/lib]、include_dirs=[‘curl/include’]
注意:在实际的使用过程中碰到过一个链接错误的坑,Setuptools在编译的时候报错:
libcurl.a:relocationagainst.rodatacannotbeusedwhenmakingasharedobject:recompilewith-fPIC libcurl.a:couldnotreadsymbols:Badvalue前面提到,python在创建扩展模块时会将源文件编译为动态链接库,动态链接库在加载的时候,内存位置是不固定的,所以我们链接的外部库代码也需要全部使用相对地址,这样代码便可以加载到内存的任意位置。因为有的库没有使用-fPIC选项进行编译,导致库最终在链接到so文件时报错。
解决方案是使用-fPIC重新编译libcurl.a库。
在编译扩展模块时,Setuptools会自动指定编译参数,比如下面一个模块的编译:
gcc-pthread-fno-strict-aliasing-O2-g-pipe-Wall-Wp,-D_FORTIFY_SOURCE=2-fexceptions-fstack-protector--param=ssp-buffer-size=4-m64-mtune=generic-D_GNU_SOURCE-fPIC-fwrapv-DNDEBUG-O2-g-pipe-Wall-Wp,-D_FORTIFY_SOURCE=2-fexceptions-fstack-protector--param=ssp-buffer-size=4-m64-mtune=generic-D_GNU_SOURCE-fPIC-fwrapv-fPIC-DDEBUG-DFOO=1-DBAR=2-DFOOBAR="abc"-Isrc-I/usr/include/python2.6-csrc/foo.cpp-obuild/temp.linux-x86_64-2.6/src/foo.o gcc-pthread-fno-strict-aliasing-O2-g-pipe-Wall-Wp,-D_FORTIFY_SOURCE=2-fexceptions-fstack-protector--param=ssp-buffer-size=4-m64-mtune=generic-D_GNU_SOURCE-fPIC-fwrapv-DNDEBUG-O2-g-pipe-Wall-Wp,-D_FORTIFY_SOURCE=2-fexceptions-fstack-protector--param=ssp-buffer-size=4-m64-mtune=generic-D_GNU_SOURCE-fPIC-fwrapv-fPIC-DDEBUG-DFOO=1-DBAR=2-DFOOBAR="abc"-Isrc-I/usr/include/python2.6-csrc/PythonWrapAPI.cpp-obuild/temp.linux-x86_64-2.6/src/PythonWrapAPI.o g++-pthread-sharedbuild/temp.linux-x86_64-2.6/src/foo.obuild/temp.linux-x86_64-2.6/src/PythonWrapAPI.o-L/usr/lib64-lpython2.6-obuild/lib.linux-x86_64-2.6/myprint.so这么多的编译参数绝大部分是Setuptools自动指定的,但是如果我们还想要在每个文件的编译再加上额外的编译选项,可以使用extra_compile_args和extra_link_args,其中extra_link_args选项用于链接。
extra_compile_args是一个编译选项字符串列表,每个编译选项都要单独作为一个字符串,不能并在一起,否则会报错。
建议使用源码分发的形式发布你的包,而不是二进制发布形式,这样包将更方便跨平台。
创建源码分发的命令为:pythonsetup.pysdist,命令执行后会创建dist目录,收集一些必要的文件以及setup脚本,生成一个压缩文件,用户安装时,只需要解压,然后执行pythonsetup.pyinstall命令,将进行编译和安装,将相应的文件存放到python第三方库目录下。
sdist比较常用的一个选项是--format,选择压缩的格式。比如,使用zip进行压缩,pythonsetup.pysdist--format=zip。
格式 后缀 zip .zip gztar .tar.gz bztar .tar.bz2 ztar .tar.Z tar .tar说明:pythonsetup.pysdist--format=zip,tar,Setuptools会分别使用zip和tar进行压缩,将同时产生两个压缩文件。
setuptools和distutils对于文件查找的算法是一样的:
所有在py_modules和packages指定的对应模块文件 所有在ext_modules和libraries选项指定的源文件和库 scripts选项指定的脚本文件 所有类似测试脚本的文件,比如:test/test*.py(低版本的包管理工具可能不支持) README.txt(或README),setup.py以及setup.cfg(README文件目前无法支持更多的后缀格式) package_data选项指定的文件 data_files选项指定的文件另外在使用过程中,遇到Setuptools的一个巨坑,确实可以包含文件,但是它并不总能包含文件,这是有前提的。
bdist是发布二进制文件,sdist是发布源文件。而在旧版本的python中(2.7以前),package_data只有在使用bdist时候才有用,也就是如果使用sdist,是无法正确包含文件的。而在新版本中,会自动把package_data里面的内容添加到MANIFEST文件中。
当我们使用sdist进行分发包时,如果需要包含额外的文件,可以使用MANIFEST.in文件,在该文件中列举出需要包含的文件。当我们执行sdist时,将会对MANIFEST.in文件进行检查,读取解释并生成MANIFEST文件,该文件列举了所有需要包含进包的文件。位于distributionroot下的MANIFEST.in文件每行对应一条包含一系列文件的命令。
本文内容总结:setuptools和setup.py,你所需要做的事&一些概念,基础概念,关于源码分发文件和二进制分发文件,示例和分发选择,purepythonmodule,package,extensionmodule,package元信息参数,package内容参数,py_modules列举每个模块,package列举每个包,package_dir重新映射package和目录的关系,install_requires和dependency_links安装依赖模块,ext_modulePython调用C/C++,name扩展模块名字,sources和include_dirs,define_macros和undef_macros,libraries和library_dirs,extra_compile_args,创建源码分发,sdist命令,MANIFEST.in模版文件,
原文链接:https://www.cnblogs.com/cposture/p/9029023.html