6. Distutils 示例?

備注

這篇文檔是歷史遺留文檔,在 https://setuptools.readthedocs.io/en/latest/setuptools.html 上的 setuptools 文檔獨(dú)立涵蓋此處包含的所有相關(guān)信息之后,將不再單獨(dú)作為正式文檔保留。

本章節(jié)提供幾個(gè)基礎(chǔ)示例,來(lái)幫助用戶入門 distutils。關(guān)于使用 distutils 的額外信息可以參考 Distutils Cookbook。

參見

Distutils Cookbook

一套展示如何更好地控制和使用 distutils 的方法。

6.1. 純 Python 分發(fā)(通過(guò) module)?

如果你要分發(fā)一組模塊,特別是它們不在特定的包中,你可以在配置腳本中使用 py_modules 選項(xiàng)單獨(dú)指定它們。

最簡(jiǎn)單的情況下,你只用關(guān)心兩個(gè)文件:一個(gè)配置腳本,和單個(gè)你要分發(fā)的模塊,這個(gè)示例中的 foo.py: :

<root>/
        setup.py
        foo.py

(在本章節(jié)的所有圖中,<root> 表示分發(fā)根目錄。)這種情況下的一個(gè)最小配置腳本是:

from distutils.core import setup
setup(name='foo',
      version='1.0',
      py_modules=['foo'],
      )

注意分發(fā)的包名用 name 選項(xiàng)單獨(dú)指定,沒(méi)有規(guī)定它必須和包中單獨(dú)的模塊同名(雖然這也是個(gè)可以遵循的好習(xí)慣)。然而,分發(fā)名用來(lái)生成文件名,所以你應(yīng)該堅(jiān)持用字母、數(shù)字、下劃線和連詞號(hào)。

因?yàn)?py_modules 是個(gè)列表,你當(dāng)然可以指定多個(gè)模塊,比如,如果你要分發(fā)模塊 foobar,你的配置可能是這樣:

<root>/
        setup.py
        foo.py
        bar.py

并且配置腳本是:

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

你可以把模塊源文件放進(jìn)另一個(gè)目錄,但是如果你有足夠的模塊,也許用包指定模塊更簡(jiǎn)單,而不是單獨(dú)列出它們。

6.2. 純 Python 分發(fā)(通過(guò) 包)?

如果你有超過(guò)一組模塊要分發(fā),特別是它們?cè)诓煌陌校苍S指定整個(gè)包更簡(jiǎn)單,而不是指定單獨(dú)的模塊。這樣即使你的模塊不在一個(gè)包中也有效;你可以直接令 Distutils 來(lái)從根包來(lái)處理模塊,并且這樣對(duì)任何其他包也有效(除非你不需要 __init__.py 文件)。

上一個(gè)示例的配置腳本也可以寫成:

from distutils.core import setup
setup(name='foobar',
      version='1.0',
      packages=[''],
      )

(空字符串表示根包。)

如果兩個(gè)文件移動(dòng)到子目錄,但是依然在根包中,如:

<root>/
        setup.py
        src/      foo.py
                  bar.py

那么你依然需要指定根包,但是你還需要告訴 Distutils 根包中的源文件在哪:

from distutils.core import setup
setup(name='foobar',
      version='1.0',
      package_dir={'': 'src'},
      packages=[''],
      )

更一般地,你要分發(fā)多個(gè)在同一個(gè)包(或者子包)中的模塊。舉個(gè)例子,假設(shè) foobar 模塊屬于 foobar 包,排布源文件樹的一種方式是:

<root>/
        setup.py
        foobar/
                 __init__.py
                 foo.py
                 bar.py

這其實(shí)是 Distutils 默認(rèn)的排布,也是你的配置腳本中需要的工作量最小的。

from distutils.core import setup
setup(name='foobar',
      version='1.0',
      packages=['foobar'],
      )

如果你要把模塊放到?jīng)]有按照它們的包名命名的目錄里,那你需要再次使用 package_dir 選項(xiàng)。比如,如果 src 目錄包含包 foobar 中的模塊:

<root>/
        setup.py
        src/
                 __init__.py
                 foo.py
                 bar.py

一個(gè)合適的配置腳本可以是:

from distutils.core import setup
setup(name='foobar',
      version='1.0',
      package_dir={'foobar': 'src'},
      packages=['foobar'],
      )

或者,你可以把主包中的模塊直接放到分發(fā)根目錄:

<root>/
        setup.py
        __init__.py
        foo.py
        bar.py

這樣你的配置文件是:

from distutils.core import setup
setup(name='foobar',
      version='1.0',
      package_dir={'foobar': ''},
      packages=['foobar'],
      )

(空字符串同樣表示當(dāng)前目錄。)

如果你有子包,則它們必須顯式列在 packages 中,但是 package_dir 中的任何條目會(huì)自動(dòng)擴(kuò)展到子包。(也就是說(shuō),Distutils 不會(huì) 掃描你的源碼樹,而是嘗試通過(guò)查找 __init__.py 文件,來(lái)弄清哪些目錄與 Python 包關(guān)聯(lián)。)這樣,如果默認(rèn)排布產(chǎn)生一個(gè)子包:

<root>/
        setup.py
        foobar/
                 __init__.py
                 foo.py
                 bar.py
                 subfoo/
                           __init__.py
                           blah.py

則相應(yīng)的配置腳本是:

from distutils.core import setup
setup(name='foobar',
      version='1.0',
      packages=['foobar', 'foobar.subfoo'],
      )

6.3. 單個(gè)擴(kuò)展模塊?

擴(kuò)展模塊用 ext_modules 選項(xiàng)指定。package_dir 對(duì)在哪尋找擴(kuò)展源文件無(wú)效;它只對(duì)純 Python 模塊的源文件有效。最簡(jiǎn)單的,一個(gè)用單個(gè)C源文件寫的單擴(kuò)展模塊是:

<root>/
        setup.py
        foo.c

如果 foo 擴(kuò)展屬于根包,則配置腳本可以是:

from distutils.core import setup
from distutils.extension import Extension
setup(name='foobar',
      version='1.0',
      ext_modules=[Extension('foo', ['foo.c'])],
      )

如果擴(kuò)展在包中,比如 foopkg,那么

使用完全相同的源文件樹排布,通過(guò)改變擴(kuò)展的名字,這個(gè)擴(kuò)展很容易放入 foopkg 包中:

from distutils.core import setup
from distutils.extension import Extension
setup(name='foobar',
      version='1.0',
      ext_modules=[Extension('foopkg.foo', ['foo.c'])],
      )

6.4. 檢查一個(gè)包?

check 命令允許你校驗(yàn)?zāi)愕陌脑獢?shù)據(jù)是否滿足生成分發(fā)的最低要求。

直接使用你的 setup.py 腳本來(lái)運(yùn)行它。如果缺了一些東西,check 會(huì)顯示警告。

我們來(lái)用單個(gè)腳本舉例:

from distutils.core import setup

setup(name='foobar')

運(yùn)行 check 命令會(huì)顯示一些警告:

$ python setup.py check
running check
warning: check: missing required meta-data: version, url
warning: check: missing meta-data: either (author and author_email) or
         (maintainer and maintainer_email) should be supplied

如果你在 long_description 域中使用 reStructuredText 語(yǔ)法,并且安裝了 docutils ,你可以用 check 命令,和 restructuredtext 選項(xiàng)檢查語(yǔ)法是否正確。

比如,如果 setup.py 腳本改成:

from distutils.core import setup

desc = """\
My description
==============

This is the description of the ``foobar`` package.
"""

setup(name='foobar', version='1', author='tarek',
    author_email='tarek@ziade.org',
    url='http://example.com', long_description=desc)

長(zhǎng)描述中有問(wèn)題的地方,通過(guò)使用 docutils 解析器,check 能進(jìn)行刪除:

$ python setup.py check --restructuredtext
running check
warning: check: Title underline too short. (line 2)
warning: check: Could not finish the parsing.

6.5. 讀取元數(shù)據(jù)?

distutils.core.setup() 函數(shù)提供一個(gè)通過(guò)項(xiàng)目的 setup.py 腳本,來(lái)查詢項(xiàng)目的元數(shù)據(jù)的域的命令行接口:

$ python setup.py --name
distribute

這個(gè)調(diào)用通過(guò)運(yùn)行 distutils.core.setup() 函數(shù)讀取 name 元數(shù)據(jù)。然而,當(dāng)源文件或者二進(jìn)制分發(fā)用 Distutils 創(chuàng)建時(shí),元數(shù)據(jù)域?qū)懭胍粋€(gè)名為 PKG-INFO 的靜態(tài)文件。當(dāng)一個(gè)基于Distutils的項(xiàng)目安裝在 Python 中時(shí),PKG-INFO 文件隨著分發(fā)的模塊和包一起復(fù)制到 NAME-VERSION-pyX.X.egg-info 中,其中 NAME 是項(xiàng)目的名字,VERSION 是元數(shù)據(jù)中定義的版本, pyX.X 則是 Python 的大版本和小版本,如 2.7 或者 3.2。

你可以讀回靜態(tài)文件,使用 distutils.dist.DistributionMetadata 類和它的 read_pkg_file() 方法:

>>>
>>> from distutils.dist import DistributionMetadata
>>> metadata = DistributionMetadata()
>>> metadata.read_pkg_file(open('distribute-0.6.8-py2.7.egg-info'))
>>> metadata.name
'distribute'
>>> metadata.version
'0.6.8'
>>> metadata.description
'Easily download, build, install, upgrade, and uninstall Python packages'

注意類也可以用元數(shù)據(jù)文件載入值來(lái)實(shí)例化:

>>>
>>> pkg_info_path = 'distribute-0.6.8-py2.7.egg-info'
>>> DistributionMetadata(pkg_info_path).name
'distribute'