python程序的打包和安裝

0 前言

我們經常會使用pip setup.py install 安裝一個源碼包,可是這個可以安裝的源碼包是怎麼生成的,這個setup.py又是怎麼生成的呢?這篇文章中,我們就來一探究竟。
用來進行python程序打包的兩個常用工具爲Distutils和setuptools,據說setuptools是Distutils的高級版本,添加了更多特性。我們就先來看一看Distutils的使用,然後再看setuptools,添加了什麼樣的內容。

1 Distutils簡介

Distutils可以用來在Python環境中構建和安裝額外(第三方)的模塊。新的模塊可以是純Python的,也可以是用C/C++寫的擴展模塊,或者可以是Python包,包中包含了由C和Python編寫的模塊。

1.1 基本術語

首先來明確幾個基本的術語:
模塊(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文件)。

1.2 Distutils術語

模塊發佈(module distribution): 一些Python模塊的集合,它們將會被一起安裝。一些常見的模塊發佈有Numeric Python,PyXML,PIL,mxBase。
純模塊發佈: 一個只包含純python模塊和包的模塊發佈。
非純模塊發佈: 至少包含一個擴展模塊的模塊發佈。
發佈根: 源碼樹的根目錄;setup.py所在的目錄。

1.3 打包的基本過程

對於模塊開發者以及需要安裝模塊的使用者來說,Distutils的使用都很簡單,作爲一個開發者,除了編寫源碼之外,還需要:

  • 編寫setup腳本(一般是setup.py);
  • 編寫一個setup配置文件(可選);
  • 創建一個源碼發佈;
  • 創建一個或多個構建(二進制)發佈(可選);

有些模塊開發者在開發時不會考慮多個平臺發佈,所以就有了packagers的角色,它們從模塊開發者那取得源碼發佈,然後在多個平臺上面進行構建,併發布多個平臺的構建版本。

1.4 一個簡單的例子

由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

2 編寫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='[email protected]',
      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

需要說明的這裏的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

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.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編譯器這樣的外部程序發生錯誤時,打印整個命令行。
————————————————
版權聲明:本文爲CSDN博主「gqtcgq」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/gqtcgq/article/details/49255995

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
————————————————
版權聲明:本文爲CSDN博主「gqtcgq」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/gqtcgq/article/details/49255995

3 配置文件

一般情況下,在構建發佈時無法將所有的選項都確定下來,有些選項的值可能來自於用戶,或者用戶的系統。這也就是配置文件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

4 源碼發佈

之前已經提到過,使用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

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

5 構建發佈(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模塊。構建發佈支持的格式如下:
在這裏插入圖片描述
當然,也可以不使用–formats選項,而是用bdist的子命令,直接創建相應的格式。比如使用bdist_dump命令可以生成所有的dumb archive格式(tar,ztar,gztar和zip),bdist_rpm會生成源碼和二進制的RPM包,bdist的子命令如下表:
在這裏插入圖片描述
具體的子命令信息,可以參閱https://docs.python.org/2/distutils/builtdist.html

6 Distutils與PYPI

PYPI,也就是Python Package Index,它是Python第三方模塊的集中營,Python開發者可以向PYPI上傳自己的Python模塊。PYPI中存放了發佈文件以及發佈的元數據。

Distutils提供了register和upload命令,來直接向PYPI推送元數據和發佈文件,詳細內容可以參閱https://docs.python.org/2/distutils/packageindex.html

7 簡單示例

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

8 其他

運行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

9 setuptools

setuptools是distutils的增強版。setuptools有一個entry_points功能很方便,類似linux啓動某個服務,如在linux命令行裏firefox能啓動火狐瀏覽器。
首先檢驗沒有安裝之前,命令path沒有作用。
在這裏插入圖片描述

9.1 創建功能包

創建一個文件夾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()

9.2 配置setup.py文件

創建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的自動下載。

9.3 打包

python setup.py sdist

在這裏插入圖片描述
打包之後多出兩個文件夾,分別是demo.egg-info和dist。demo.egg-info是必要的安裝信息,而dist中的壓縮包就是安裝包。
在這裏插入圖片描述
查看dist/demo-0.1.tar.gz解壓之後的文件。
在這裏插入圖片描述

9.4 安裝包

在這裏插入圖片描述
另外安裝時使用如下命令,可以保存安裝日誌,方便日後的卸載

 python setup.py install --record log

9.5 使用包

安裝之後在命令行中直接輸入path,回車能夠看到調用了get_path.py中的函數fun(),輸出字符串。
在這裏插入圖片描述
同時也可以導入使用。
在這裏插入圖片描述

9.6 刪除包

windows : for /F  %i in (log) do del %i 
linux : cat log | xagrs rm -rf

原文鏈接:

  1. Python深入:Distutils發佈Python模塊
  2. python打包工具distutils、setuptools分析
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章