4. 構建C/C++擴展?
一個(gè)CPython的C擴展是一個(gè)共享庫(例如一個(gè)Linux上的 .so
,或者Windows上的 .pyd
),其會(huì )導出一個(gè) 初始化函數 。
為了可導入,共享庫必須在 PYTHONPATH
中有效,且必須命名遵循模塊名字,通過(guò)適當的擴展。當使用distutils時(shí),會(huì )自動(dòng)生成正確的文件名。
初始化函數的聲明如下:
該函數返回完整初始化過(guò)的模塊,或一個(gè) PyModuleDef
實(shí)例。查看 Initializing C modules 了解更多細節。
對于僅有ASCII編碼的模塊名,函數必須是 PyInit_<modulename>
,將 <modulename>
替換為模塊的名字。當使用 Multi-phase initialization 時(shí),允許使用非ASCII編碼的模塊名。此時(shí)初始化函數的名字是 PyInitU_<modulename>
,而 <modulename>
需要用Python的 punycode 編碼,連字號需替換為下劃線(xiàn)。在Python里:
def initfunc_name(name):
try:
suffix = b'_' + name.encode('ascii')
except UnicodeEncodeError:
suffix = b'U_' + name.encode('punycode').replace(b'-', b'_')
return b'PyInit' + suffix
可以在一個(gè)動(dòng)態(tài)庫里導出多個(gè)模塊,通過(guò)定義多個(gè)初始化函數。而導入他們需要符號鏈接或自定義導入器,因為缺省時(shí)只有對應了文件名的函數才會(huì )被發(fā)現。查看 "一個(gè)庫里的多模塊" 章節,在 PEP 489 了解更多細節。
4.1. 使用distutils構建C和C++擴展?
擴展模塊可以用distutils來(lái)構建,這是Python自帶的。distutils也支持創(chuàng )建二進(jìn)制包,用戶(hù)無(wú)需編譯器而distutils就能安裝擴展。
一個(gè)distutils包包含了一個(gè)驅動(dòng)腳本 setup.py
。這是個(gè)純Python文件,大多數時(shí)候也很簡(jiǎn)單,看起來(lái)如下:
from distutils.core import setup, Extension
module1 = Extension('demo',
sources = ['demo.c'])
setup (name = 'PackageName',
version = '1.0',
description = 'This is a demo package',
ext_modules = [module1])
通過(guò)文件 setup.py
,和文件 demo.c
,運行如下
python setup.py build
這會(huì )編譯 demo.c
,然后產(chǎn)生一個(gè)擴展模塊叫做 demo
在目錄 build
里。依賴(lài)于系統,模塊文件會(huì )放在某個(gè)子目錄形如 build/lib.system
,名字可能是 demo.so
或 demo.pyd
。
在文件 setup.py
里,所有動(dòng)作的入口通過(guò) setup
函數。該函數可以接受可變數量個(gè)關(guān)鍵字參數,上面的例子只使用了一個(gè)子集。特別需要注意的例子指定了構建包的元信息,以及指定了包內容。通常一個(gè)包會(huì )包括多個(gè)模塊,就像Python的源碼模塊、文檔、子包等。請參數distutils的文檔,在 分發(fā) Python 模塊(遺留版本) 來(lái)了解更多distutils的特性;本章節只解釋構建擴展模塊的部分。
通常預計算參數給 setup()
,想要更好的結構化驅動(dòng)腳本。有如如上例子函數 setup()
的 ext_modules
參數是一列擴展模塊,每個(gè)是一個(gè) Extension
類(lèi)的實(shí)例。例子中的實(shí)例定義了擴展命名為 demo
,從單一源碼文件構建 demo.c
。
更多時(shí)候,構建一個(gè)擴展會(huì )復雜的多,需要額外的預處理器定義和庫。如下例子展示了這些。
from distutils.core import setup, Extension
module1 = Extension('demo',
define_macros = [('MAJOR_VERSION', '1'),
('MINOR_VERSION', '0')],
include_dirs = ['/usr/local/include'],
libraries = ['tcl83'],
library_dirs = ['/usr/local/lib'],
sources = ['demo.c'])
setup (name = 'PackageName',
version = '1.0',
description = 'This is a demo package',
author = 'Martin v. Loewis',
author_email = 'martin@v.loewis.de',
url = 'https://docs.python.org/extending/building',
long_description = '''
This is really just a demo package.
''',
ext_modules = [module1])
例子中函數 setup()
在調用時(shí)額外傳遞了元信息,是推薦發(fā)布包構建時(shí)的內容。對于這個(gè)擴展,其指定了預處理器定義,include目錄,庫目錄,庫。依賴(lài)于編譯器,distutils還會(huì )用其他方式傳遞信息給編譯器。例如在Unix上,結果是如下編譯命令
gcc -DNDEBUG -g -O3 -Wall -Wstrict-prototypes -fPIC -DMAJOR_VERSION=1 -DMINOR_VERSION=0 -I/usr/local/include -I/usr/local/include/python2.2 -c demo.c -o build/temp.linux-i686-2.2/demo.o
gcc -shared build/temp.linux-i686-2.2/demo.o -L/usr/local/lib -ltcl83 -o build/lib.linux-i686-2.2/demo.so
這些行代碼僅用于展示目的;distutils用戶(hù)應該相信distutils能正確調用。
4.2. 發(fā)布你的擴展模塊?
當一個(gè)擴展已經(jīng)成功地被構建時(shí),有三種方式來(lái)使用它。
最終用戶(hù)通常想要安裝模塊,可以這么運行
python setup.py install
模塊維護者應該制作源碼包;要實(shí)現可以運行
python setup.py sdist
有些情況下,需要在源碼發(fā)布包里包含額外的文件;這通過(guò) MANIFEST.in
文件實(shí)現,查看 Specifying the files to distribute 了解細節。
如果源碼發(fā)行包被成功地構建,維護者還可以創(chuàng )建二進(jìn)制發(fā)行包。 取決于具體平臺,以下命令中的一個(gè)可以用來(lái)完成此任務(wù)
python setup.py bdist_rpm
python setup.py bdist_dumb