來編寫你的 setup 腳本(一)

前言

相比於日常工作中用各種輪子,想必造輪子才是各位同學的興趣所在,Python 的輪子百花齊放,但是有一點卻是相同的。對於 Python 庫的發布,都需要寫一個 setup 腳本才能順利進行。

本文大部分內容翻譯自官方文檔,如果有不對或者翻譯得較為生硬的地方還請指出。

一個簡單的 setup 腳本

# example 1nfrom distutils.core import setupnsetup(name=foo,n version=1.0,n py_modules=[foo],n )n

setup 腳本是所有 Python 模塊構建、發布和安裝的核心。編寫 setup 腳本的主要目的是為了向 distutils (Python 自帶的分發庫)描述你的分發需求以使你的模塊可以得到相應的處理。如例1所示,setup 腳本主要由對於 setup() 函數的調用組成,並且大多數開發者所要提供給 distutils 的信息都被以關鍵詞參數的形式提供給了 setup()。

關於包和源碼的映射

以下是一個些許複雜的例子:

#!/usr/bin/env pythonn# example2nfrom distutils.core import setupnnsetup(name=Distutils,n version=1.0,n description=Python Distribution Utilities,n author=Greg Ward,n author_email=gward@python.net,n url=https://www.python.org/sigs/distutils-sig/,n packages=[distutils, distutils.command],n )n

可以看到,例2所針對的就是 disutils 包(只是作為例子使用)。與先前的例1相比例2有兩點不同,首先,腳本里有了更多的元信息。其次,腳本里有了更多對於 Python 包的限制而不是對於 Python 模塊的限制。這是很重要的,因為 distutils 由許多模塊組成(目前分成了兩個包)一個指明所有模塊的列表會是冗長並且不易維護的。

需要注意的是,寫入 setup 腳本的任何路徑(包括文件和目錄)都應該用 Unix 風格來寫(比如說用 『/』 分割)。distutils 包會負責將他們轉換為適用於你當前平台的風格。這點將會使你的setup 腳本具有跨平台的能力,而這也是 disutils 包的主要目的之一。當然,這些都是指提供給 distutils 函數的路徑。對於你模塊中用到的路徑,還請儘可能避免硬編碼。

packages 選項告訴 distutils 對於所有 packages 列表裡提到的純 Python 模塊做處理(編譯,分發或者安裝等等)。為了實現這個功能,需要在 setup 腳本里有一個包名到目錄的映射。默認的機制相當簡單,對於 setup 腳本所在目錄下同名的目錄即視為包所在目錄。也就是說,當你在 setup 腳本中寫入 packages = [foo] 時你應當確保可以在 setup 腳本的同級目錄下 distutils 可以找到 foo/__init__.py。如果沒有找到對應文件,disutils 不會直接報錯,而是給出一個告警然後繼續進行有問題的打包流程。

如果你用了一個不同名的目錄存放源碼,你只需要在 setup 腳本中提供 package_dir 選項闡明映射關係即可。比如說,你將所有源碼置於 lib 目錄下,所以本應在頂層包的模塊都在 lib 目錄下,在 foo 這個包的模塊現在都在 lib/foo 目錄下,所以你應當在 setup 腳本中寫入:

package_dir = {: lib}n

字典中的鍵值代表了包的名字,空的包名則代表頂層的包。值則代表了對於 setup 腳本所在目錄的相對路徑,在這個例子中,當你寫入 packages = [foo] 時,你其實是指 lib/foo/__init__.py 這個文件存在。

另一種方法則是直接將 foo 這個包的內容全部放入 lib 而不是在 lib 下建一個 foo 目錄,這樣的話 foo.bar 包就在 lib/bar 下,在 setup 腳本中,要實現這個效果可以這麼寫:

package_dir = {foo: lib}n

一個在 package_dir 字典中的 package: dir 映射會對當前包下的所有包都生效, 所以 foo.bar 會自動生效. 在這個例子當中, packages = [foo, foo.bar] 告訴 distutils 去尋找 lib/__init__.py 和 lib/bar/__init__.py.

對於一個相對較小的模塊的發布,你可能更想要列出所有模塊而不是列出所有的包,尤其是對於那種根目錄下就是一個簡單模塊的類型。對於這種類型你可以這麼做:

py_modules = [mod1, pkg.mod2]n

這描述了兩個包,一個在根目錄下,另一個則在 pkg 目錄下。默認的「包:目錄」映射關係表明你可以在 setup 腳本所在的路徑下找到 mod1.py 和 pkg/mod2.py, 當然,你也可以用 package_dir 選項重寫這層映射關係就是了。

關於另外一些支持的選項

除了包名和版本以外,setup 腳本還有很多選項可寫,如下所示:

Notes:

  1. 這些選項是必須的
  2. 推薦採用這種格式來描寫版本號 major.minor[.patch[.sub]].
  3. 作者和作者的郵箱是需要的. 如果維護者選項不為空, 對於 PKG-INFO 協議, distutils 會把他作為作者
  4. 如果你的代碼要兼容 2.2.3 或 2.3 之前的 python 代碼,這些選項是不能使用的. 詳情可見 PyPI .
  5. 當你註冊一個包的時候 long_description 選項會被 PyPI 寫入一個包的主頁。
  6. license 表明了這個包受哪種策略保護,對於他人能否在商業項目中使用你的代碼或者有權修改你的代碼等等有所限制,請謹慎挑選協議。

『short string』單行不超過200字的文字『long string』reStructuredText 格式的富文本 (詳情請見 Docutils: Documentation Utilities).『list of strings』請看下文.

所有的文字都不應該是 Unicode 的.

版本號選擇是一門藝術. Python 包堅持使用 major.minor[.patch][sub] 的格式。例如:

0.1.0這個包的第一版1.0.1a2第二個 alpha 版本的發布,發布內容是關於 1.0 版本de

另外一些比較重要的選項:

除了這些我還要著重介紹三個選項,可能是因為版本關係,這三個選項在文檔中並沒有被提到

install_requires:

列表的形式描述安裝這個包所需要的依賴,可以通過相應代碼做到根據 Python 版本來安裝合適的依賴,下面就是一個針對 MySQLdb 和 pymysql 的例子(MySQLdb 目前還不支持 Python3)

#!/usr/bin/env pythonnimport osnimport sysnimport sysconfignninstall_requires = [n python-logstash==0.4.6,n redis==2.10.5,n tornado==4.4.2]nnif float(sysconfig.get_python_version()) < 3:n install_requires.append(MySQL-python==1.2.5)nelse:n install_requires.append(PyMySQL==0.7.9)n

然後在 setup 函數的調用中加上 install_requires=install_requires 即可。

tests_require:

原理和 install_requires 類似,只不過這個是用來表明做單元測試時所需要的依賴(一般用在持續集成的時候),例如在 setup 函數的調用中加上 tests_require=[pytest]。

cmdclass:

這個選項使定製化命令成為了可能,你可以通過繼承 setuptools.command 下的命令類來進行定製化,還是以單元測試為例:

try:n from setuptools import setupn from setuptools.command.test import test as TestCommandnn class PyTest(TestCommand):n def finalize_options(self):n TestCommand.finalize_options(self)n self.test_args = []n self.test_suite = Truenn def run_tests(self):n # import here, because outside the eggs arent loadedn import pytestn errno = pytest.main(self.test_args)n sys.exit(errno)nnexcept ImportError:nn from distutils.core import setupnn def PyTest(x):n xn

然後在 setup 函數的調用中加上 cmdclass={test: PyTest} 就可以通過在命令行調用

python setup.py test 來運行你的單元測試了

關於 classifiers

classifiers 是在列表中被制定的:

setup(...,n classifiers=[n Development Status :: 4 - Beta,n Environment :: Console,n Environment :: Web Environment,n Intended Audience :: End Users/Desktop,n Intended Audience :: Developers,n Intended Audience :: System Administrators,n License :: OSI Approved :: Python Software Foundation License,n Operating System :: MacOS :: MacOS X,n Operating System :: Microsoft :: Windows,n Operating System :: POSIX,n Programming Language :: Python,n Topic :: Communications :: Email,n Topic :: Office/Business,n Topic :: Software Development :: Bug Tracking,n ],n )n

如果你既想在 setup.py 中使用分級器又想 兼容 2.2.3 之前的 Python 版本, 那麼你可以在 setup.py 調用 setup() 之前的代碼里加入下面的代碼段。

# patch distutils if it cant cope with the "classifiers" orn# "download_url" keywordsnfrom sys import versionnif version < 2.2.3:n from distutils.dist import DistributionMetadatan DistributionMetadata.classifiers = Nonen DistributionMetadata.download_url = Nonen

github 廣告時間

項目地址: NoneGG/aredis

aredis 是一款基於 Python async/await 原語的非同步的 redis 客戶端。

現在項目經歷了一次重構後對 client 和 command 兩塊做了拆分,下周準備開始使用 cython 對於其中能加速的代碼試著加速看看。

前排求 star 求 pr 求建議,如果大家有什麼好玩的點子歡迎給我提 issue,如果試用下來碰到什麼奇怪的問題也請一定給我反饋,我最晚會在每周周末的時候修復並且給個反饋。


推薦閱讀:

基於Git的文件自動同步的思考和實現
Python在unpacking上的一個小陷阱
用 Python 玩轉 Facebook 數據
Tornado 非同步非阻塞淺析

TAG:Python | Python开发 | Python框架 |