標籤:c++ python c++與python互動 boost.python c++調用python
之前很長的一段時間裡,無論寫什麼程式,我都是使用C++,因為C++非常強大,從底層到上層都可以搞。底層方面C++對C相容、支援內聯彙編,可以開發嵌入式程式、驅動程式、作業系統,上層方面C++支援物件導向,有非常豐富的程式庫,足以開發出任何程式。C++使用QT、Android NDK等工具可以開發手機應用,使用CGICC等工具可以開發Web程式。但用C++來開發手機應用和Web程式並不是個好選擇,也沒有多少人這樣做。C++什麼都可以搞,但不意味著什麼都可以方便的搞。選擇合適的語言工具來開發相應的程式,發揮出每種語言在各個領域的優勢,並讓各種語言分工合作才是王道。
剛開始踏入編程這一行的時候,我選擇的是C/C++語言作為入門語言,因為對底層的東西比較感興趣,並且打算將來搞嵌入式、驅動程式、虛擬機器、作業系統或伺服器方面的開發。在使用C/C++開發程式的過程中,經常與系統API打交道,甚至突破系統API的限制實現了作業系統沒有公開提供一些功能,感覺非常爽快。後來有一段時間用C#重寫我的一些C++程式的時候,由於.NET裡沒有提供我想要的那些功能(畢竟C#是微軟用來和JAVA競爭的語言,.NET裡封裝的最多的還是那些用於上層應用開發的工具庫),需要大量的調用系統API,本來在C++下調用系統API一句話就能搞定的東西,在C#下需要聲明一大堆東西,還需要處理類型轉換和指標的問題,結果就是代碼寫起來非常噁心,從那以後就對C#沒有什麼好感。不過C#給我印象較好的一點就是上層工具庫很豐富,調用簡單。之前在寫一些XX管理系統的作業的時候,用winform,嘩嘩嘩,各種控制項往上拖,改一改屬性,關聯一下資料庫,基本代碼都沒寫,資料庫中的資料就以報表形式顯示在介面上了。不過我對XX管理系統之類的公司專屬應用程式開發和Web程式開發之類的上層應用開發興趣不大,所以就沒有繼續深入學習C#了。
話說回來,雖然C/C++在底層方面開發的能力很強,但它並不是一門開發效率高的語言,畢竟運行效率與開發效率不可兼得,沒有什麼東西能兼顧方方面面做到十全十美,凡事有得必有失。這樣一來,在寫一些比較上層的功能的時候,就感覺很累,很吃力,所以一直想找一門語言與C/C++配合工作,發揮C/C++在底層的優勢並能提高開發效率。於是我便開啟最近的程式設計語言熱門排行榜,從上至下看一看選擇什麼語言作為我的第二程式設計語言比較好。首先是JAVA和C#,由於對行動裝置 App開發、公司專屬應用程式開發、Web開發興趣不大,所以否決掉了,並且好像也沒有多少領域需要JAVA與C/C++合作或C#與C/C++合作。然後是Objective-C,由於不準備搞行動裝置 App開發,當然也包括IOS的行動裝置 App開發,所以否決掉了。接下來是Python、Javascript、PHP、VB.NET、VB,VB就不說了,VB.NET的話不如搞C#了,PHP和Javascript主要用於Web開發方面,所以都否決掉了,那排名前10的語言就只剩Python了。10名以後的語言由於用的人不是很多(不火),或者是領特定領域語言,或是由於我孤陋寡聞不認識,所以就都否決掉了。
粗略瞭解了一下Python,是一門指令碼語言,開發效率比C#、JAVA還高,並且能和C/C++很好的結合在一起,那當然是最佳之選了。之前就聽很多人推薦Python,一直以為不就是一門指令碼語言嘛,指令碼語言那麼多,Python能牛到哪去,而且看了下Python寫的代碼,發現文法與C的文法有不少差別,感覺怪怪的,就沒有去過多瞭解。後來找了幾本書學了下,並且動手寫了寫代碼,頓時大吃一驚,原來程式也可以寫得這麼簡潔方便。
馬上動手用Python寫了一個從Web上爬取資料的小程式,再和以前用C++寫過的同樣功能的程式對比了一下,頓時倒吸一口涼氣,褲襠一下子就濕了——嚇尿了。在此貼出這段Python代碼,只用了10多行就實現了從愛密碼網上爬取每天最新發行的共用迅雷會員帳號密碼。
import urllib.requestimport redef get_member(): response = urllib.request.urlopen("http://521xunlei.com/portal.php") html = str(response.read(), 'gbk') result = re.search(r'<div id="portal_block_62_content"[\s\S]+?<a href="(thread.+?)"[\s\S]+?</li>', html) if result is None: return None url = r'http://521xunlei.com/' + result.group(1) response = urllib.request.urlopen(url) html = str(response.read(), 'gbk') pattern = re.compile(r'(?:帳號共用|迅雷會員帳號分享|迅雷號|迅雷帳號|迅雷共用號|迅雷會員帳號|共用帳號)([a-zA-Z0-9]+?:[12]).*?(?:分享密碼|密碼分享|首發密碼)(.+?)(?:<br|</font>)') result = pattern.findall(html) if len(result) == 0: return None else: return result
我之前也用C++寫過Web爬蟲,可謂是費了很大的功夫。首先是需要一個基於HTTP協議訪問網站的庫,C++標準庫與Boost庫中都沒有。到網上找一找,有個開源跨平台的libcurl可用,不過和大多數C/C++的第三方庫一樣,這些庫在VC編譯器上編譯總是會有各種問題。折騰了大半天好不容易編譯成功了,看一下文檔,全英文的,而且不是很詳細,而且libcurl的API似乎也不支援Unicode,總之用起來各種不順心。後來改用windows提供的Wininet和WinHttp,雖然用起來感覺好了點,但畢竟是C語言的API,而且沒有提供易用的簡化介面,發送一個GET請求都得寫十幾行代碼。後來花了幾天時間把WinHttp封裝成了一個類,提供簡單易用的介面來發送GET/POST請求,用起來才感覺方便了點。可是網頁的HTML文本擷取到以後,如何從中提取資料。剛開始想到的是HTML解析器,嘗試使用第三方的HTML解析庫和微軟提供的MSHTML,遇到的各種噁心事情,就不再提了,在另外一篇文章中已經提過了。為了使用MSHTML,可謂是把基本的COM技術和ATL都學了,花了多長時間就不說了。再後來,又花了好幾天吧MSHTML的準系統封裝成了一個類,可以像javascript那樣用簡短的語句讀取HTML元素的內容。總結一下,為了用C++寫Web爬蟲,總共寫了兩個類分別封裝了WinHTTP和MSHTML,還有一些方便易用的轉換字元集的函數和小工具,總共寫了3000多行代碼。做這些準備工作,查閱了很多資料,學了多門技術,花了很長很長的時間。然後才可以比較愉快的寫Web爬蟲了,不過這個時候已經很疲憊了…
後來看到別人用Python結合Regex寫Web爬蟲,自己也跟著試了下,果然很簡單很過癮。之所以寫起來很爽,主要有三點:1.Python文法簡潔,代碼寫出來很短小2.Python標準庫很強大,各種各樣的模組都有,尤其是Web訪問、文本處理方面更是它的強項 3.對於定向的Web爬蟲,其實不用上HTML解析器,使用Regex也能達到目的,而且所需要的代碼量也小,另外Regex中的分組捕獲功能相當好用,直接一次性的就把整個HTML文本中想要的資料擷取出來了。
其實Python讓我最驚喜的是它的標準庫提供了很多東西,功能比較全面,我想要的東西都有了,而且這些庫調用起來很簡單,只需import一下就行了,第三方庫也只需easy_install一下就可簡單安裝起來。C/C++的第三方庫雖然更多更豐富,但是編譯、整合比較麻煩,尤其是在Windows平台上的VC編譯器中編譯,十有八九要蹦出一大堆編譯錯誤,這對於我們這樣的新手來說非常頭疼,簡直是一場災難。而且各種庫都喜歡各自搞一套資料類型,比如STL/MFC/QT等各種庫都各自有各自的string類型,還有字元集問題,有的庫不支援Unicode,有的又只支援UTF8等等,想要把各種庫整合到自己的程式中很麻煩。Python中則不需要管這些噁心的事情,整合各種庫的噁心的工作已經被別人包辦了,你只需要伸手拿過來用即可。
之前就說到,沒有什麼語言是萬能,Python也不是搞什麼都好搞,比如程式GUI。上面的10幾行代碼雖然實現了迅雷帳號的爬取,但如果需要給程式加一個圖形介面。用Python就沒有那麼簡單易用了,Python的大多數GUI庫都是調用其它語言的GUI庫,比如Python也可以調用Win32 API和MFC來構建GUI,但肯定沒有原生的C/C++調用起來方便。並且Python似乎也沒有簡單易用的可視化GUI設計工具。再加上我剛接觸Python,也不太熟悉Python的GUI開發,於是決定用我熟悉的MFC構建GUI,然後讓C++調用上面的10幾行Python代碼,完成一個“迅雷帳號擷取器”。順便也瞭解一下C++和Python的互動方式。
程式碼就不貼了,這篇文章主要是講使用Python的感受。完成了C++調用Python的程式後,我發現了一個讓人興奮的事情。那就是MFC寫好圖形介面後,那個EXE以後完全不需要改動了。MFC程式只是調用Python的一個函數,函數返回一個帳號密碼數組,MFC程式將這些帳號密碼顯示在列表框中。只要保證Python提供給MFC程式的這個介面不變,那麼MFC程式永遠都不需要修改,不需要重新編譯。由於擷取到的迅雷帳號肯定是一個帳號和一個密碼構成的數組,所以可以保證這個介面不變。如果以後“愛密碼”網站改版了,採集資料的代碼失效了(這種事很容易發生),或者是不再在“愛密碼”網上採集資料,而是改為到其它網站上採集資料,只需要修改Python代碼即可,不會影響到MFC程式,不需要改動MFC程式。這就是模組化編程的優點。不過模組這個東西,很多人認為一般是一個DLL。比如這個程式,如果用C++實現資料擷取功能並封裝到一個DLL裡給MFC程式調用,以後如果需要改動採集資料的代碼,只需重新編譯DLL即可,不會影響到MFC寫的EXE。其實指令碼語言也可以作模組,稱為指令碼模組。指令碼模組的優勢更明顯,修改的時候用文字編輯器開啟改一改儲存一下就OK了。使用者在使用你的軟體的時候,如果你的軟體需要更新,更新文本化的指令碼模組比更新二進位的DLL模組會方便很多。
說到C++與Python的互動,最基礎的做法是調用Python虛擬機器提供的C語言API。因為Python虛擬機器本來就是C語言寫的,官方也提供了豐富的API,使得C/C++可以訪問Python中的幾乎所有東西。但是這組API調用起來並不方便。比如要調用上面的Python代碼中的get_member()方法擷取迅雷帳號,get_member()的返回值是一個list,這個list的每個元素是一個tuple,每個tuple中儲存著兩個字串,一個是帳號一個是密碼。調用的代碼如下:
PyObject *module_name = Py_BuildValue("s", "thunder");PyObject *module = PyImport_Import(module_name);PyObject *function_dict = PyModule_GetDict(module);PyObject *function = PyDict_GetItemString(function_dict, "get_member");PyObject *result_list = PyObject_CallObject(function, NULL);int result_num = PyList_Size(result_list);for (int i = 0; i < result_num; i++){PyObject *member_tuple = PyList_GetItem(result_list, i);wcout << PyUnicode_AsUnicode(PyTuple_GetItem(member_tuple, 0)) << L"\t"<< PyUnicode_AsUnicode(PyTuple_GetItem(member_tuple, 1)) << L"\n";}Py_DecRef(module);
可見調用起來是很麻煩的,還要注意資源釋放問題。如果換成Python調用Python的話是非常簡潔的(好像是廢話):
import thunderresult = thunder.get_member()for iter in result: print(iter[0] + '\t' + iter[1] + '\n')
不過一個讓人興奮的事情就是Boost.Python改變了這一現狀,Boost.Python使得無論是C++調用Python還是Python調用C++都方便了很多,使用Boost.Python調用如下:
object result = import("thunder").attr("get_member")();for (int i = 0; i < len(result); i++){cout << string(extract<string>(result[i][0])) << "\t"<< string(extract<string>(result[i][1])) << "\n";}
可以看出Boost.Python對Python函數get_member()的調用,一行代碼就完成了,並且使用了智能指標技術,不需要管資源釋放問題。代碼寫出來簡潔了很多,其實Boost裡很多庫都使得C++代碼寫起來更簡潔。這得益於C++強大模板技術(被稱為編譯時間的多態),以及Boost的那幫開發人員牛逼的技巧,把模板玩得生龍活虎、出神入化,如果你讀過Boost的原始碼,一定會覺得非常佩服。C++本來是一門強型別的靜態語言,但是Boost藉助模板、運算子多載技術,使得你在使用Boost.Python之類的一些庫的時候,感覺C++好像變成了一門弱類型的動態語言。比如Boost.Python中的object封裝了Python中的一切“東西“,一個object對象可以是Python中的一個模組、一個Python對象、一個函數、一個字串、一個整型變數、一個list、一個tuple或一個dict等等任何類型的執行個體。一切類型都弱化了,只有一種類型:object。並且Boost重載了object的operator()和operator[],使得一個object對象可以當數組、當list、當tuple、當dict用,也可以當一個函數調用,而在編譯時間並不關心它到底是不是一個數組、是不是一個list、是不是一個函數,一切都放到了運行時才決定。並且使用起來在文法上也接近指令碼語言,非常簡單易用。這真是一大神器啊,藉助Boost.Python,C++與Python的合作開發方便極了。
Python初探