淺談Python程序與C++程序的聯(lián)合使用
作為Python程序員,應(yīng)該能夠正視Python的優(yōu)點(diǎn)與缺點(diǎn)。眾所周之,Python的運(yùn)行速度是很慢的,特別是大數(shù)據(jù)量的運(yùn)算時(shí),Python會(huì)慢得讓人難以忍受。對(duì)于這種情況,“專業(yè)”的解決方案是用上numpy或者opencl。不過有時(shí)候?yàn)榱艘稽c(diǎn)小功能用上這種重型的解決方案很不劃算,或者有時(shí)候想要實(shí)現(xiàn)的操作在numpy里面沒有,需要我們自己用C語(yǔ)言來編寫??傊?,我們使用Python與C++的混合編程能夠加快程序熱點(diǎn)的運(yùn)算速度。
首先要提醒大家注意的是,在考慮聯(lián)合編程之前一定要找到程序運(yùn)行的熱點(diǎn)。簡(jiǎn)單一點(diǎn)地,使用標(biāo)準(zhǔn)庫(kù)的profile或者cProfile模塊找到最消耗CPU的位置,如果這個(gè)位置只簡(jiǎn)單的消耗IO時(shí)間,通常換成C++程序的意義也不會(huì)很大,此時(shí)做聯(lián)合編程可能是事倍功半,起不到多大的效果。
還有些情況,Python程序員們想要使用操作系統(tǒng)或者外部模塊提供的函數(shù)。這些模塊一般是為C/C++程序員提供的。這時(shí)候也是Python與C++聯(lián)合編程的用武之地。
Python語(yǔ)言可以說是最好的膠水語(yǔ)言。僅就與C++聯(lián)合編程這個(gè)問題來講,依使用難度與功能來排列,Python社區(qū)提供了以下幾種解決方案:
1.使用標(biāo)準(zhǔn)庫(kù)ctypes直接調(diào)用C/C++編寫的動(dòng)態(tài)鏈接庫(kù)。這是最簡(jiǎn)單易用的方案。C/C++程序員使用自己的豐富的經(jīng)驗(yàn),把預(yù)定的功能實(shí)現(xiàn)為動(dòng)態(tài)鏈接庫(kù)。而Python程序員只要知道這些動(dòng)態(tài)鏈接庫(kù)函數(shù)的名稱、參數(shù)類型與返回值類型就能簡(jiǎn)單地調(diào)用它。當(dāng)你傳入?yún)?shù)時(shí),ctypes模塊會(huì)自動(dòng)地把Python的對(duì)象成為C/C++所對(duì)應(yīng)的參數(shù)類型。比如以下調(diào)用Windows的API:
#定義參數(shù)類型與函數(shù)名稱 from ctypes.wintypes import UINT, DWORD GetLastInputInfo = ctypes.windll.user32.GetLastInputInfo class LASTINPUTINFO(ctypes.Structure): _fields_ = [("cbSize", UINT), ("dwTime", DWORD)] #開始調(diào)用DLL導(dǎo)出的函數(shù) def getLastInputTime_nt(): info = LASTINPUTINFO() info.cbSize = ctypes.sizeof(info) info.dwTime = 0 if not GetLastInputInfo(ctypes.byref(info)): raise WindowsError("") return info.dwTime
在這里展示了如何構(gòu)造Windows的API所需要的結(jié)構(gòu)體,如何填充結(jié)構(gòu)體并分析返回值。
ctypes還能將Python函數(shù)提供給C/C++代碼作為回調(diào)函數(shù)。
與其它解決方案相比。ctypes不需要程序員熟悉C/C++語(yǔ)言,不需要安裝一個(gè)C/C++的編譯器,它通過操作系統(tǒng)的接口直接操作C/C++代碼。而且ctypes是標(biāo)準(zhǔn)庫(kù)的一部分,只要安裝了Python就可以直接使用。這幾個(gè)原因使得它深受Python程序員的喜愛。
而它的劣勢(shì)呢。首先,ctypes不能簡(jiǎn)單調(diào)用C++程序,因?yàn)镃++在編譯的時(shí)候使用了name mangling這個(gè)技術(shù)來實(shí)現(xiàn)函數(shù)的重載。C++會(huì)自動(dòng)地為類的成員函數(shù)加上類名前綴。所以,C++程序員需要以C語(yǔ)言的調(diào)用約定來提供接口,沒有類,沒有重載函數(shù),沒有模板,沒有C++異常。不能直接調(diào)用現(xiàn)有的C++代碼可能是這個(gè)方案最大的缺點(diǎn)。
另外,對(duì)于list, set之類的數(shù)據(jù)類型,ctypes不能識(shí)別并自動(dòng)地在Python與C/C++數(shù)據(jù)類型之間轉(zhuǎn)換。C/C++部分不能識(shí)別Python數(shù)據(jù)類型,這時(shí)候只能用Python語(yǔ)言來編寫轉(zhuǎn)換代碼。如果數(shù)據(jù)量較大,或者調(diào)用很頻繁,轉(zhuǎn)換代碼反而會(huì)浪費(fèi)很多的資源。這或許是ctypes的另一個(gè)劣勢(shì)之一了。
2.如果你使用的是Jython或者IronPython的話,它們也提供了類似于ctypes之類的模塊,能夠直接訪問Java或者.Net語(yǔ)言編寫的模塊。其優(yōu)勢(shì)與劣勢(shì)大致與ctypes相似。因?yàn)槠涫褂梅秶邢?,這里不再詳述。
3.使用Cython語(yǔ)言,一種類似于Python語(yǔ)言的一種新型語(yǔ)言編寫預(yù)定功能的代碼,然后將這些代碼轉(zhuǎn)換成為C語(yǔ)言編譯成為Python語(yǔ)言可以直接調(diào)用的二進(jìn)制模塊。Cython語(yǔ)言是融合Python語(yǔ)言與C語(yǔ)言的一種新型語(yǔ)言。它本身能夠理解Python語(yǔ)言的語(yǔ)法,然后在其基礎(chǔ)上增加了某些C語(yǔ)言的語(yǔ)法,以便更精細(xì)地控制數(shù)據(jù)類型與指針。基本兼容Python語(yǔ)法是這個(gè)解決方案最大的特點(diǎn)。很多時(shí)候,Python程序員只要在舊的代碼中簡(jiǎn)單地聲明一下代碼中所使用的參數(shù)、變量的類型,就能把立即為舊的Python程序提速。
Cython提供了一個(gè)名為pyximporter的工具,能夠在安裝了C/C++編譯器的計(jì)算機(jī)上面為簡(jiǎn)單的Cython程序直接生成相應(yīng)的Python模塊。這使得Cython的使用與普通的Python程序一樣簡(jiǎn)單。比如下面這段代碼,直接保存為myhello.pyx即可被調(diào)用。
#myhello.pyx def sayHelloTenTimes(): cdef int i #只要簡(jiǎn)單地為變量標(biāo)識(shí)類型即可加速循環(huán)。 for i in range(0, 10): print("hello, world!") $ python >>> import pyximport; pyximport.install() >>> import myhello >>> myhello.sayHelloTenTimes()
由此可見,Cython非常容易使用。而且不僅能夠處理C語(yǔ)言的模塊,還能處理C++的模塊——雖然沒有直接支持虛函數(shù)之類的完整C++特性。因?yàn)樗恢苯邮褂肅/C++語(yǔ)法,而是另外設(shè)計(jì)比C/C++更簡(jiǎn)潔優(yōu)雅的新型語(yǔ)法,因此,對(duì)于不熟悉C/C++的程序員來說有很大的吸引力。相比ctypes來說,因?yàn)閰?shù)類型轉(zhuǎn)換更加智能與高效,所以通常能夠提升更多的效率。
劣勢(shì)呢,所謂用Python程序員所熟練的語(yǔ)法來編寫高速的運(yùn)算代碼,乍一聽相當(dāng)?shù)赜形Α5侨绻胍钊氲乜刂苾?nèi)存與數(shù)據(jù)結(jié)構(gòu)時(shí),程序員可能會(huì)發(fā)現(xiàn),現(xiàn)在他不得不熟練地掌握C/C++語(yǔ)言,然后用Cython的語(yǔ)法寫出來。以程序員們懶惰的性格,這反而是件難以忍受的事件。這或許是Cython本身并不大流行的主要原因吧。
4.使用boost.python。有意思的是,與ctypes/Cython形成鮮明的對(duì)比,boost.python傾向于讓C++程序員擁有更熟悉的編程環(huán)境。它讓C++程序員使用他所熟悉的C++語(yǔ)法直接控制Python的數(shù)據(jù)結(jié)構(gòu),調(diào)用Python的解釋器。它沒有像Cython那樣發(fā)明新的語(yǔ)法,而是直接使用C++的語(yǔ)法,編寫供Python使用的接口。與Cython同樣的道理,它的效率優(yōu)勝于ctypes。
與Cython/SWIG/SIP等方案相比,程序員只需要學(xué)習(xí)C/C++與Python兩種語(yǔ)言。另外,與本文提到的幾種解決方案相比,它非常適合在主要由C++編寫的程序中控制Python代碼。不僅功能更強(qiáng)大、效率還更高。如此神奇的解決方案會(huì)有什么劣勢(shì)呢?某些人可能不同意吧,老魚一聽說它依賴于boost就蔫了,感覺編譯與學(xué)習(xí)龐大又奇怪的boost非常浪費(fèi)生命。
5.使用SWIG或者SIP,通過編寫一個(gè)接口文件,使用類似于C/C++語(yǔ)法——聲明函數(shù)、類型的信息,然后使用特殊的工具為C/C++的代碼生成Python的接口代碼。這些接口代碼能夠在Python與C/C++之間的數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換。最終編譯這些接口代碼,成為Python的二進(jìn)制模塊。SWIG與SIP的接口文件與C/C++的頭文件非常相似。
這兩種工具差不多,因?yàn)椤1举|(zhì)上,他們都與Cython類似,都使用了中間語(yǔ)言來生成轉(zhuǎn)換代碼。但SWIG/SIP能夠在他們的接口文件中嵌入C/C++,能夠讓程序員仔細(xì)地調(diào)節(jié)數(shù)據(jù)類型的轉(zhuǎn)換過程。在使用上,它比Cython的層次更低,更接近于Python本身提供的API。
SWIG能夠?yàn)槎喾N腳本語(yǔ)言生成轉(zhuǎn)換代碼。而SIP則專門針對(duì)Python與C++。此外,SIP本身是作為PyQt的專門工具來開發(fā)的,因此它能夠理解Qt的signal/slot。從應(yīng)用項(xiàng)目上來看,SWIG似乎會(huì)更廣泛一點(diǎn)。而SIP,目前所見的項(xiàng)目基本都與PyQt相關(guān)。據(jù)說SWIG對(duì)于C++的支持不好,不知道有沒有人來說一下呢。相比之下,SIP對(duì)于C++的支持非常完善,諸如虛函數(shù)、protected member function、模版、析構(gòu)函數(shù)、異常等特性都得到良好的支持。而且SIP支持Python的GIL,還擁有一個(gè)使用Python編寫的編譯系統(tǒng)。可能會(huì)更方便一點(diǎn)。
然而這種方案畢竟要學(xué)習(xí)一種新的語(yǔ)言,所以從表面上來看不如Cython和boost.python討喜。當(dāng)程序員想要仔細(xì)地調(diào)節(jié)類型轉(zhuǎn)換代碼的時(shí)候,需要學(xué)習(xí)SWIG/SIP的內(nèi)部機(jī)制,被限定使用特殊的變量名。這使得這種方案的學(xué)習(xí)曲線相對(duì)較高。
6.直接使用Python的API,可以稱之為最終解決方案。Cython, SWIG, SIP的接口文件轉(zhuǎn)換后所生成的C/C++代碼實(shí)際上都使用Python的API。與其它方案相比,這種方案相當(dāng)?shù)胤睆?fù),必須為每次函數(shù)調(diào)用編寫數(shù)據(jù)轉(zhuǎn)換代碼,還要操心Python對(duì)象的引用計(jì)數(shù)。我覺得這種方案一無是處,這時(shí)就不再多講了。其它的工具pybindgen不知道什么情況。有興趣的話可以看看。
好了。題外話一句吧,我一直覺得ctypes與xmlrpc并列Python語(yǔ)言的兩大神器,最能體現(xiàn)Python的生產(chǎn)效率。
希望本文在大家選擇一種技術(shù)路線時(shí)能提供一點(diǎn)點(diǎn)幫助。
- Python調(diào)用C/C++動(dòng)態(tài)鏈接庫(kù)的方法詳解
- Python 調(diào)用VC++的動(dòng)態(tài)鏈接庫(kù)(DLL)
- c++生成dll使用python調(diào)用dll的方法
- 詳解python如何調(diào)用C/C++底層庫(kù)與互相傳值
- 將Python代碼嵌入C++程序進(jìn)行編寫的實(shí)例
- 深入淺析 C++ 調(diào)用 Python 模塊
- C++、python和go語(yǔ)言實(shí)現(xiàn)的簡(jiǎn)單客戶端服務(wù)器代碼示例
- 通過C++學(xué)習(xí)Python
- Python調(diào)用C++程序的方法詳解
- Python和C/C++交互的幾種方法總結(jié)
相關(guān)文章
python分段函數(shù)的實(shí)現(xiàn)示例
分段函數(shù)是一種數(shù)學(xué)函數(shù),它將定義域分成若干個(gè)區(qū)間,每個(gè)區(qū)間對(duì)應(yīng)一個(gè)函數(shù),本文主要介紹了python分段函數(shù)的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12使用DataFrame實(shí)現(xiàn)兩表連接方式
這篇文章主要介紹了使用DataFrame實(shí)現(xiàn)兩表連接方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08python3.5 tkinter實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn)
這篇文章主要為大家詳細(xì)介紹了python3.5 tkinter實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01python匿名函數(shù)lambda原理及實(shí)例解析
這篇文章主要介紹了python匿名函數(shù)lambda原理及實(shí)例解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02Python Locust負(fù)載測(cè)試工具安裝配置使用詳解
本文將提供有關(guān)Python Locust的全面指南,包括安裝和配置、基本概念、性能測(cè)試、任務(wù)編寫、報(bào)告生成以及實(shí)際應(yīng)用場(chǎng)景,將通過豐富的示例代碼來幫助深入理解Locust的使用2024-01-01