今天星期五,很高興立刻將開啓愉快的週末時光,今天要介紹的是指引了無數讀者入門並提升的Python殿堂的神書《Python核心編程(第3版)》中文版累計銷售超20萬冊。他被譽爲提升Python技能的必讀書,書中全面涵蓋當今應用開發中的衆多領域爲中級Python開發人員提供實踐方法,經過目錄脈絡慢慢了解。另外異步社區招募書評人,後臺回覆「書評」,加入咱們。前端
大約10多年之前,我在一家名爲Four11的公司接觸到Python。當時,該公司有一個主要的產品——Four11.com White Page目錄服務。它們使用Python來設計該產品的下一代:Rocketmail Web E-mail服務,該服務最終演變爲今天的Yahoo!Mail。python
學習Python並加入最初的Yahoo!Mail工程團隊是一件至關有趣的事情。我幫助從新設計了地址簿和拼寫檢查程序。在當時,Python也成爲其餘Yahoo!站點的一部分,其中包括People Search、Yellow Pages、Maps和Driving Directions等。事實上,我當時是People Search部門的首席工程師。linux
儘管在當時Python對我而言是全新的,可是它也很容易學習—比我過去學習的其餘語言都要簡單。在當時,Python教程的缺少迫使我使用Library Reference和Quick Reference Guide做爲主要的學習工具,而這也是促使我寫做本書的一個驅動力。程序員
從我在Yahoo!的日子開始,我可以以各類有趣的方式在隨後的工做中使用Python。在任何狀況下,我都能使用Python的強大功能來及時地解決遇到的問題。我也開發了多門Python課程,並使用本書來說授那些課程—徹底使用本身的做品。正則表達式
圖書不只是卓越的Python學習資料,它們仍是用來說解Python的最佳工具。做爲一名工程師,我知道學習、理解和應用一種新技術所須要的東西。做爲一名專業講師,我也知道爲客戶提供最有效的會話(session)所須要的是什麼。這些圖書栩栩如生,同時包含你沒法從「純粹的培訓師」或「純粹的圖書做者」那裏得到的提示。算法
不一樣於嚴格的「入門」圖書或者純粹的「重口味」計算機科學參考圖書,我過去的教學經驗告訴我,一本易於閱讀同時又面向技術的圖書應該服務於這樣的一個目的,即可以讓人儘量迅速地掌握Python,以便能將其應用到十萬火急的任務上來。咱們在介紹概念時會輔之以合適的案例,以加速學習過程。每章最後都會給出大量練習,旨在夯實你對書中概念和理念的理解。數據庫
可以與Bruce Eckel的寫做風格相提並論,我很激動也很謙卑(見本書第1版的評論,網址爲http://corepython.com)。本書並不是一本枯燥的大學教材,咱們的目標是營造一個與你交談的環境,就像你是在參加個人一個廣受好評的Python培訓課程同樣。做爲一名終身學習的學生,我不斷地因材施教,告訴你須要學習什麼才能快速、完全地掌握Python的概念。你也將發現,能夠快速、輕鬆地閱讀本書,並且不會錯失任何技術細節。編程
做爲一名工程師,我知道應該怎樣作才能向你講授Python中的概念。做爲一名教師,我能夠將技術細節所有打散,而後轉換成一種易於理解和迅速掌握的語言。你將從個人寫做風格和教學風格中獲益,更重要的是,你會喜歡上用Python來編程。windows
所以,你也將注意到,儘管我是本書惟一的做者,可是我使用的是「第三人稱」的寫做風格,也就是說,我使用了諸如「咱們」這樣的一些廢話,緣由是在學習本書的過程當中,咱們是一塊兒的,共同朝着擴展Python編程技能的目標而努力。數組
在本書第1版剛問世時,Python剛發佈了2.0版本。從那時起,Python語言發生了重大的改進,Python語言被愈來愈多的人接受,其使用率也大幅提高。Python編程語言大獲成功。Python語言的缺陷已被刪除,並且有新的特性不斷加入,這將全世界Python開發人員的能力和編程修養提高到了一個新的水平。本書第2版於2006年問世,當時也是Python的鼎盛時期,它的版本是迄今爲止最爲流行的2.5版本。
本書第2版問世以後好評如潮,其銷量超過了第1版。在那期間,Python自己也贏得了無數榮譽,包括下面這些。
Tiobe(www.tiobe.com)
——年度編程語言(2007年、2010年)
LinuxJournal(linuxjournal.com)
——最喜歡的編程語言(2009~2011年)
——最喜歡的腳本語言(2006~2008年、2010年、2011年)
LinuxQuestions.org會員選擇獎
——年度編程語言(2007~2010年)
這些獎項和榮譽推進着Python進一步發展。如今,Python已經進入了下一代:Python 3。一樣,本書也在向着其「第三代」前進。我很是高興Prentice Hall可以讓我寫做本書第3版。因爲Python 3.x版本不可以後向兼容Python 1和Python 2,所以還須要一段時間,Python 3.x才能被業界全面採用和集成進來。咱們很樂意引導你經歷這個過渡。本書第3版的代碼也適用於Python 2和Python 3(視狀況而定——並不是全部代碼都移植了過來)。在移植代碼時,咱們還會討論各類工具和作法。
Python 3.x版本帶來的挑戰延續着對Python編程語言進行迭代和改進的趨勢,要移除Python語言最後的重大缺陷還有很長的路要走,並且在不斷演變的Python語言中移除重大缺陷也是一個至關大的飛躍。與之類似,本書的結構也作出了至關重大的轉變。限於篇幅和範圍,已出版的第2版沒法處理第3版中引入的全部新內容。
所以,Prentice Hall和我想到了一個好方法來向前推動本書,即從邏輯上將其拆分爲兩部分,其中一部分講述Python核心語言主題,另外一部分講述高級應用主題,並由此將書拆分爲兩卷。而你手頭上當前拿着的這本書是
(第3版)的第二部分。好消息是因爲第二部分的內容已經至關完整齊備,所以第一部分的內容也就沒有存在的必要了。要閱讀本書,咱們建議讀者可以擁有Python中級編程經驗。若是你最近已經學過Python,並且可以至關輕鬆地駕馭它,或者你已經具有Python技能,可是但願能進一步提高該技能,那麼你算是找對圖書了。
的讀者都知道,個人主要目標是以一種全面的方式來說解Python語言的本質,而非僅僅是其語法(學習Python的語法貌似也不須要一本書)。在知道了Python的工做機制以後—包括數據對象和內存管理之間的關係—你將成爲一名更高效的Python程序員。而這是第一部分(即
)要作的工做。
與本書全部版本同樣,我會繼續更新圖書的Web站點以及博客,以確保不管你移植到哪一個新發布的Python版本,均可以讓本書作到與時俱進。
對以前的讀者來講,本書第3版新增了下述主題:
基於Web的E-mail示例(第3章);
使用Tile/Ttk(第5章);
使用MongoDB(第6章);
更重要的Outlook和PowerPoint示例(第7章);
Web服務器網關接口(WSGI)(第10章);
使用Twitter(第13章);
使用Google+(第15章)。
此外,咱們還在當前版本中添加了全新的3章,分別是第11章、第12章和第14章。這幾章表明着常用Python進行應用開發的一些新領域或正在進行的領域。全部的現有章節已經面目一新,並更新到Python的最新版本,同時還包含了一些新內容。經過隨後的「章節指南」部分,你能夠了解到本書每部分要講解的內容。
在本書中,你將會用到從其餘地方學習到的全部Python知識,並培養新的技能,從而構建本身的工具箱。藉助於該工具箱,你可以使用Python開發各類類型的應用程序。關於高級主題的章節旨在快速概述各類不一樣的主題。若是你開始轉向這些章節中涵蓋的特定應用開發領域,你將會發現它們不只給出了正確的方向,還包含了更多的信息。可是不要期待有一個深刻的解決方案,由於這有悖於本書的初衷—提供更爲普遍的解決方案。
與其餘全部
圖書同樣,本書一樣包含了許多示例,你能夠在計算機上進行嘗試。爲了牢固掌握概念,你也會在每章最後發現有趣、有挑戰性的練習。這些初級和中級難度的練習旨在測試你的知識掌握狀況,提高你的Python技能。畢竟,沒有什麼能夠替代實踐經驗。咱們相信,你不只可以學到不少Python編程技能,同時還能在儘量短的時間內迅速掌握它們。
對咱們來說,擴展Python技能的最佳方式就是動手練習,所以你會發現這些練習是本書的一個最大優點。它們能夠測試你對每章主題和定義的掌握狀況,並激勵你儘量多地動手編程。除了本身編寫應用程序以外,沒有其餘方法能夠更有效地提高你的編程技能。你須要解決初級、中級和高級難度的編程問題。並且你應該須要編寫一個大型的應用程序(這也是不少讀者想要在本書中看到的),而不是採用一些腳原本實現。坦白說,你可能作得沒有那麼好,可是經過親自動手實踐,你的收穫會更大。附錄A給出了每章中某些練習的答案。附錄B包含了一些有用的參考表。
感謝全部讀者的反饋和鼓勵,大家是我寫做這些圖書的動力。但願大家能繼續給我發送反饋信息,並促使本書第4版儘快問世,並且其質量優於以前全部版本。
本書分爲3部分。其中第1部分佔據了本書2/3的篇幅,它講解了應用開發工具箱中(固然,Python是關注重點)「核心」成員的解決方案。第2部分講解了與Web編程相關的各類主題。第3部分是補充部分,它提供了一些仍然在開發過程當中的實驗章節,在本書後續版本中,這些章節有望成爲獨立的章節。
本書提供了一些高級主題,以展現Python能夠用來開發什麼應用程序。值得高興的是,本書起碼能夠向你提供Python開發中許多關鍵領域的入門知識,其中包括以前版本中提到的一些主題。
下面是本書每章的內容簡介。
正則表達式是一種功能強大的工具,它能夠用來進行模式匹配、提取、查找和替換。
現在許多應用都是面向網絡的。該章將介紹如何使用TCP/IP與UDP/IP來建立客戶端和服務器,以及如何快速入門SocketServer和Twisted。
現在在用的大多數Internet協議都是使用套接字開發的。該章將探究一些用來構建Internet協議客戶端的高級庫。該章重點討論的是FTP、Usenet消息協議(NNTP)以及各類E-mail協議(SMTP、POP3及IMAP4)。
多線程編程是一種經過引入併發來提高多種應用程序執行性能的方式。該章經過解釋概念並展現正確建立Python多線程應用程序的方法、什麼是最佳用例來說解如何在Python中實現線程。
Tkinter(在Python 3中重名爲tkinter)以Tk圖形工具包爲基礎,是Python中的默認GUI開發庫。該章經過演示如何建立簡單的GUI應用來介紹Tkinter。一種最佳的學習方式是複製,並在某些應用的頂層進行建立,這樣能夠很快上手。該章最後簡要討論其餘圖形庫,好比Tix、Pmw、wxPython、PyGTK和Ttk/Tile。
Python也有助於簡化數據庫編程。該章首先回顧一些基本概念,而後介紹Python數據庫應用編程接口(DB-API)。隨後介紹如何使用Python鏈接到關係數據庫,並執行查詢和操做。若是你更喜歡使用結構化查詢語言(SQL)的放手管理方法(hands-off approach),並且只是想在無須考慮底層數據庫層的狀況下處理對象,則可使用對象-關係映射。最後,該章以MongoDB做爲NoSQL示例介紹了非關係數據庫。
不管喜歡與否,咱們都生活在一個不得不和Microsoft Windows PC打交道的世界。咱們可能偶爾與它們打交道,也可能天天都要接觸到它們,可是不管處於哪一種狀況下,均可以使用Python的強大功能來讓生活更輕鬆一些。該章將探究使用Python來編寫COM客戶端,以控制Office應用程序(好比Word、Excel、PowerPoint和Outlook)並與它們進行通訊。儘管該章在本書以前版本中是實驗章節,可是咱們很高興可以爲其添加足夠的內容,使其單獨成章。
前面提到,可以重用代碼並對語言進行擴展將具備至關強大的功能。在純Python中,這些擴展是模塊和包,可是你也可使用C/C++、C#或Java來開發底層的代碼。這些擴展可以以無縫方式與Python相接。用低級編程語言來編寫本身的擴展能夠提高性能,並加強安全性(由於源代碼沒有必要泄露)。該章講解使用C語言來開發擴展的整個過程。
該章將擴展第2章討論的客戶端/服務器架構,咱們將這一律念應用到Web上。該章不只探究客戶端,還介紹用來解析Web內容的各類Web客戶端工具。最後,該章介紹如何使用Python來定製本身的Web服務器。
Web服務器的主要工做是接受客戶端的請求,而後返回結果。可是服務器如何得到客戶端的請求數據呢?因爲服務器只擅長返回結果,所以它們一般沒有獲取數據的能力或邏輯,因而這個工做須要在他處完成。CGI給了服務器生成另一個程序的能力,讓這個程序來進行數據處理(長久以來一直也是這麼作的),可是該程序不具有擴展性,所以並不會在實踐中使用。可是,不管使用的是什麼框架,這一律念仍然適用,所以咱們將用一章的篇幅來學習CGI。該章介紹WSGI如何經過通用編程接口來爲應用開發人員提供幫助。此外,該章還將介紹當框架開發人員須要在一端鏈接Web服務器而應用程序的代碼放在另一端時,WSGI如何提供幫助,以便應用開發人員可以在無須擔憂執行平臺的狀況下編寫代碼。
Python有不少Web框架,Django是其中最爲流行的一個。該章介紹這個框架,而後介紹如何編寫簡單的Web應用。在具有了這些知識後,你能夠自行研究其餘Web框架。
雲計算在IT業界引起了轟動。儘管像Amazon的AWS這樣的基礎設施服務和Gmail、Yahoo!Mail這樣的在線應用等在當今世界中更爲常見,可是有不少平臺憑藉其強大的功能,成爲這些服務的替代者。這些平臺充分利用了基礎設施,無須用戶介入,並且要比雲軟件具備更多的靈活性,緣由是你能夠自行控制應用及其代碼。該章全面介紹使用Python的第一個平臺服務——Google App Egnine。在掌握了該章的內容後,你能夠探討該章介紹的其餘相似服務。
該章介紹Web上的高級服務(使用HTTP)。該章先介紹一個較爲古老的服務(Yahoo!Finance),而後再給出一個較新的服務(Twitter)。該章討論如何使用Python以及前面學到的知識來與這些服務進行交互。
這是本書的第一個補充章節,它介紹使用Python來處理文本的方法。該章先介紹CSV,而後是JSON,最後是XML。在該章最後一節,咱們將前面學到的客戶端/服務器知識融合到XML中,以查看如何使用XML-RPC來建立在線的遠程過程調用(RPC)。
該章包含一些附加材料,這些內容可能會在本書下一版中成爲單獨的章節。該章討論的主題包含Java/Jython和Google+。
導 讀 第8章 擴展Python
C語言效率很高。但這種效率的代價是須要用戶親自進行許多低級資源管理工做。因爲如今的機器性能很是強大,這種親歷親爲是得不償失的。若是能使用一種在機器執行效率較低而用戶開發效率很高的語言,則是很是明智的。Python就是這樣的一種語言。
——Eric Raymond,1996年10月
本章內容:
簡介和動機;
編寫Python擴展;
相關主題。
本章將介紹如何編寫擴展代碼,並將其功能集成到Python編程環境中。首先介紹這樣作的動機,接着逐步介紹如何編寫擴展。須要指出的是,雖然Python擴展主要用C語言編寫,且出於通用性的考慮,本節的全部示例代碼都是純C語言代碼。由於C++是C語言的超集,因此讀者也可使用C++。若是讀者使用Microsoft Visual Studio構建擴展,須要用到Visual C++。
本章第一節將介紹什麼是Python擴展,並嘗試說明什麼狀況下須要(或不須要)考慮建立一個擴展。
通常來講,任何能夠集成或導入另外一個Python腳本的代碼都是一個擴展。這些新代碼可使用純Python編寫,也可使用像C和C++這樣的編譯語言編寫(在Jython中用Java編寫擴展,在IronPython中用C#或VisualBasic.NET編寫擴展)。
核心提示:在不一樣的平臺上分別安裝客戶端和服務器來運行網絡應用程序!
這裏須要提醒一下,通常來講,即便開發環境中使用了自行編譯的Python解釋器,Python擴展也是通用的。手動編譯和獲取二進制包之間存在着微妙的關係。儘管編譯比直接下載並安裝二進制包要複雜一些,可是前者能夠靈活地定製所使用的Python版本。若是須要建立擴展,就應該在與擴展最終執行環境類似的環境中進行開發。
本章的示例都是在基於UNIX的系統上構建的(這些系統一般自帶編譯器),但這裏假定讀者有可用的C/C++(或Java)編譯器,以及針對C/C++(或Java)的Python開發環境。這二者的惟一區別僅僅是編譯方法。而擴展中的實際代碼可通用於任何平臺上的Python環境中。
若是是在Windows平臺上開發,須要用到Visual C++開發環境。Python發行包中自帶了7.1版的項目文件,但也可使用老版本的VC++。
關於構建Python擴展的更多信息請查看下面的網址。
針對PC上的C++:docs.python.org/extending/w…
Java/Jython:wiki.python.org/jython
IronPython–ironpython.codeplex.com
警告:
儘管在相同架構下的不一樣計算機之間移動二進制擴展通常狀況下不會出現問題,可是有時編譯器或CPU之間的細微差異可能致使代碼不能正常工做。
Python中一個很是好的特性是,不管是擴展仍是普通Python模塊,解釋器與其交互方式徹底相同。這樣設計的目的是對導入的模塊進行抽象,隱藏擴展中底層代碼的實現細節。除非模塊使用者搜索相應的模塊文件,不然他就不會知道某個模塊是使用Python編寫,仍是使用編譯語言編寫的。
簡要縱觀軟件工程的歷史,編程語言過去一直都根據原始定義來使用。只能使用語言定義的功能,就沒法向已有的語言添加新的功能。然而,在現今的編程環境中,可定製性編程是很吸引人的特性,它能夠促進代碼重用。Tcl和Python就是第一批這樣可擴展的語言,這些語言可以擴展其語言自己。那麼爲何須要擴展像Python這樣已經很完善的語言呢?有下面幾點充分的理由。
須要Python沒有的額外功能:擴展Python的緣由之一是須要該語言核心部分沒有提供一些的新功能。使用純Python或編譯後的擴展均可以作到這一點,不過像建立新的數據類型或在已有應用中嵌入Python,就必須使用編譯後的模塊。
改善瓶頸性能:衆所周知,因爲解釋型語言的代碼在運行時即時轉換,所以執行起來比編譯語言慢。通常來講,將一段代碼移到擴展中能夠提高整體性能。但問題在於,若是轉移到擴展中,有時代價會太高。
從性價比的角度來看,先對代碼進行一些簡單的性能分析,找出瓶頸所在,而後將這些瓶頸處的代碼移到擴展中是個更聰明的方式。這樣既能更快地得到效率提高,也不會花費太多的資源。
隱藏專有代碼:建立擴展的另外一個重要緣由是腳本語言的缺陷。全部這樣易用的語言都沒有關注源碼的私密性,由於這些語言的源碼自己就是可執行程序。
將代碼從Python中轉到編譯型語言中能夠隱藏這些專有代碼,由於後者提供的是二進制文件。編譯過的文件相對來講不易進行逆向工程,這樣就將源碼隱藏起來了。在涉及特殊算法、加密或軟件安全性時這,這就顯得十分重要。
另外一個保證代碼私有的方式是隻提供預編譯的.pyc文件。在提供實際代碼(.py文件)和將代碼遷移到擴展這兩種方法之間,這是比較好的折中。
在真正介紹如何編寫擴展以前,還要了解什麼狀況下不該該編寫擴展。這一節至關於一個告誡,不然讀者會認爲做者一直在爲擴展Python作虛假宣傳。是的,編寫擴展有前面提到的那些優勢,但也有一些缺點。
必須編寫C/C++代碼。
須要理解如何在Python和C/C++之間傳遞數據。
須要手動管理引用。
還有一些封裝工具能夠完成相同的事情,這些工具能夠生成高效的C/C++代碼,但用戶又無須手動編寫任何C/C++代碼就可使用這些代碼。本章末尾將介紹其中一些工具。不要說我沒提醒過你!下面繼續……
爲Python編寫擴展主要涉及三個步驟。
1.建立應用代碼。
2.根據樣板編寫封裝代碼。
3.編譯並測試。
本節將深刻了解這三個步驟。
首先,全部須要成爲擴展的代碼應該組成一個獨立的「庫」。換句話說,要明白這些代碼將做爲一個Python模塊存在。所以在設計函數和對象時須要考慮Python代碼與C代碼之間的交互和數據共享,反之亦然。
下一步,建立測試代碼來保證代碼的正確性。甚至可使用Python風格的作法,即將main()函數放在C中做爲測試程序。若是代碼編譯、連接並加載到一個可執行程序中(而不是共享庫文件),調用這樣的可執行程序能對軟件庫進行迴歸測試。下面將要介紹的擴展現例都使用這種方法。
測試用例包含兩個須要引入Python環境中的C函數。一個是遞歸階乘函數fac()。另外一個是簡單的字符串逆序函數reverse(),主要用於「原地」逆序字符串,即在不額外分配字符串空間的狀況下,逆序排列字符串中的字符。因爲這些函數須要用到指針,所以須要仔細設計並調試這些C代碼,以防將問題帶入Python。
第1版的文件名爲Extest1.c,參見示例8-1。
示例8-1 純C版本的庫(Extest1.c)
這段代碼含有兩個函數:fac()和reverse(),用來實現前面所說的功能。fac()接受一個整型參數,而後遞歸計算結果,最後從遞歸的最外層返回給調用者。
最後一部分是必要的main()函數。它用來做爲測試函數,將不一樣的參數傳入fac()和reverse()。經過這個函數能夠判斷前兩個函數是否能正常工做。
如今編譯這段代碼。許多類UNIX系統都含有gcc編譯器,在這些系統上可使用下面的命令。
要運行代碼,能夠執行下面的命令並得到輸出。
再次強調,必須儘量先完善擴展程序的代碼。把針對Python程序的調試與針對擴展庫自己bug的調試混在一塊兒是一件很是痛苦的事情。換句話說,將調試核心代碼與調試Python程序分開。與Python接口的代碼寫得越完善,就越容易把它集成進Python並正確工做。
這裏每一個函數都接受一個參數,也只返回一個參數。這簡單明瞭,所以集成進Python應該不難。注意,到目前爲止,還沒涉及任何與Python相關的內容。僅僅建立了一個標準的C或C++應用而已。
完整地實現一個擴展都圍繞「封裝」相關的概念,讀者應該熟悉這些概念,如組合類、修飾函數、類委託等。開發者須要精心設計擴展代碼,無縫鏈接Python和相應的擴展實現語言。這種接口代碼一般稱爲樣板(boilerplate)代碼,由於若是須要與Python解釋器交互,會用到一些格式固定的代碼。
樣板代碼主要含有四部分。
1.包含Python頭文件。
2.爲每個模塊函數添加形如PyObject*
()的封裝函數。
3.爲每個模塊函數添加一個PyMethodDef
[]數組/表。
4.添加模塊初始化函數
()。
首先要作的是找到Python包含文件,並確保編譯器能夠訪問這個文件的目錄。在大多數類UNIX系統上,Python包含文件通常位於/usr/local/include/python2.x或/usr/include/python2.x中,其中2.x是Python的版本。若是經過編譯安裝的Python解釋器,應該不會有問題,由於系統知道安裝文件的位置。
將Python.h這個頭文件包含在源碼中,以下所示。
這部分很簡單。下面須要添加樣板軟件中的其餘部分。
這一部分有點難度。對於每一個須要在Python環境中訪問的函數,須要建立一個以static PyObject\*標識,以模塊名開頭,緊接着是下劃線和函數名自己的函數。
例如,若要讓fac()函數能夠在Python中導入,並將Extest做爲最終的模塊名稱,須要建立一個名爲Extest_fac()的封裝函數。在用到這個函數的Python腳本中,可使用import Extest和Extest.fac()的形式在任意地方調用fac()函數(或者先from Extest import fac,而後直接 調用fac())。
封裝函數的任務是將Python中的值轉成成C形式,接着調用相應的函數。當C函數執行完畢時,須要返回Python的環境中。封裝函數須要將返回值轉換成Pytho形式,並進行真正的返回,傳回全部須要的值。
在fac()的示例中,當客戶程序調用Extest.fac()時,會調用封裝函數。這裏會接受一個Python整數,將其轉換成C整數,接着調用C函數fac(),獲取返回結果,一樣是一個整數。將這個返回值轉換成Python整數,返回給調用者(記住,編寫的封裝函數就是def fac(n)聲明的代理函數。當這個封裝函數返回時,就至關於Python fac()函數執行完畢了)。
如今讀者可能會問,怎樣才能完成這種轉換?答案是在從Python到C時,調用一系列的PyArg_Parse*()函數,從C返回Python時,調用Py_BuildValue()函數。
這些PyArg_Parse*()函數與C中的sscanf()函數相似。其接受一個字節流,而後根據一些格式字符串進行解析,將結果放入到相應指針所指的變量中。若解析成功就返回1;不然返回0。
Py_BuildValue()的工做方式相似sprintf(),接受一個格式字符串,並將全部參數按照格式字符串指定的格式轉換爲一個Python對象。
表8-1總結了這些函數。
表8-1 在Python和C/C++之間轉換數據
在Python和C之間使用一系列的轉換編碼來轉換數據對象。轉換編碼見表8-2。
表8-2 Python①和C/C++之間的「轉換編碼」
格式編碼
Python數據類型
① Python 2和Python 3之間的格式編碼基本相同。
② 與「O」相似,但不遞增對象的引用計數。
這些轉換編碼用在格式字符串中,用於指出對應的值在兩種語言中應該如何轉換。注意,其轉換類型不可用於Java中,Java中全部數據類型都是類。能夠閱讀Jython文檔來了解Java類型和Python對象之間的對應關係。對於C#和VB.NET一樣如此。
這裏列出完整的Extest_fac()封裝函數。
封裝函數中首先解析Python中傳遞進來的參數。這裏應該是一個普通的整型變量,因此使用「i」這個轉換編碼來告知轉換函數進行相應的操做。若是參數的值確實是一個整型變量,則將其存入num變量中。不然,PyArg_ParseTuple()會返回NULL,在這種狀況下封裝函數也會返回NULL。此時,它會生成TypeError異常來通知客戶端用戶,所需的參數應該是一個整型變量。
接着使用num做爲參數調用fac()函數,將結果放在res中,這裏重用了res變量。如今構建返回對象,即一個Python整數,依然經過「i」這個轉換編碼。Py_BuildValue()建立一個整型Python對象,並將其返回。這就是封裝函數的全部內容。
實際上,當封裝函數寫多了後,就會試圖簡化代碼來避免使用中間變量。儘可能讓代碼保持可讀性。這裏將Extest_fac()函數精簡成下面這個更短的版本,它只用了一個變量num。
那reverse()怎麼實現?因爲已經知道如何返回單個值,這裏將對reverse()的需求稍微修改下,返回兩個值。將以元組的形式返回一對字符串,第一個元素是傳遞進來的原始字符串,第二個是新逆序的字符串。
爲了更靈活地調用函數,這裏將該函數命名爲Extest.doppel(),來表示其行爲與reverse()有所不一樣。將C代碼封裝進Extest_doppel()函數中,以下所示。
在Extest_fac()中,接受一個字符串值做爲輸入,將其存入orig_str中。注意,選擇使用「s」這個轉換編碼。接着調用strdup()來建立該字符串的副本。(由於須要返回原始字符串,同時須要一個字符串來逆序,因此最好的選擇是直接複製原始字符串。)strdup()建立並返回一個副本,該副本當即傳遞給reverse()。這樣就得到逆序後的字符串。
如你所見,Py_BuildValue()使用轉換字符串「ss」將這兩個字符串放到了一塊兒。這裏建立了含有原始字符串和逆序字符串的元組。都結束了嗎?尚未。
這裏遇到了C語言中一個危險的東西:內存泄露(分配了內存但沒有釋放)。內存泄露就至關於從圖書館借書,可是沒有歸還。在獲取了某些資源後,當再也不須要時,必定要釋放這些資源。咱們怎麼能在代碼中犯這樣的錯誤呢(雖然看上去很無辜)?
當Py_BuildValue()將值組合到一個Python對象並返回時,它會建立傳入數據的副本。在這裏的例子中,建立了一對字符串。問題在於分配了第二個字符串的內存,但在結束時沒有釋放這段內存,致使了內存泄露。而實際想作的是構建返回值,接着釋放在封裝函數中分配的內存。爲此,必須像下面這樣修改代碼。
這裏引入了dupe_str變量來指向新分配的字符串並構建返回對象。接着使用free()來釋放分配的內容,並最終返回給調用者。如今纔算真正完成。
既然兩個封裝函數都已完成,下一步就須要在某個地方將函數列出來,以便讓Python解釋器知道如何導入並訪問這些函數。這就是
Methods[]數組的任務。
這個數組由多個子數組組成,每一個子數組含有一個函數的相關信息,母數組以NULL數組結尾,表示在此結束。對Extest模塊來講,建立下面這個ExtestMethods[]數組。
首先給出了在Python中訪問所用到的名稱,接着是對應的封裝函數。常量METH_VARARGS表示參數以元組的形式給定。若是使用PyArg_ParseTupleAndKeywords()來處理包含關鍵字的參數,須要將這個標記與METH_KEYWORDS常量進行邏輯OR操做。最後,使用一對NULL來表示結束函數信息列表,還表示只含有兩個函數。
最後一部分是模塊初始化函數。當解釋器導入模塊時會調用這段代碼。這段代碼中只調用了Py_Init
()函數,其第一個參數是模塊名稱,第二個是
Methods[]數組,這樣解釋器就能夠訪問模塊函數。對於Extest模塊,其initExtest()過程以下所示。
如今已經完成了全部封裝任務。將Extest1.c中原先的代碼與全部這些代碼合併到一個新文件Extest2.c中。至此,就完成了示例中的全部開發步驟。
另外一種建立擴展的方式是先編寫封裝代碼,使用存根(stub)函數、測試函數或假函數,在開發的過程當中將其替換成具備完整功能的實現代碼。經過這種方式,能夠保證Python和C之間接口的正確性,並使用Python來測試相應的C代碼。
如今進入了編譯階段。爲了構建新的Python封裝擴展,須要將其與Python庫一同編譯。(從2.0版開始)擴展的編譯步驟已經跨平臺標準化了,簡化了擴展編寫者的工做。如今使用distutils包來構建、安裝和發佈模塊、擴展和軟件包。從Python 2.0開始,這種方式替換了老版本1.x中使用makefile構建擴展的方式。使用distutils,能夠經過下面這些簡單的步驟構建擴展。
1.建立setup.py。
2.運行setup.py來編譯並連接代碼。
3.在Python中導入模塊。
4.測試函數。
第一步就是建立setup.py文件。大部分編譯工做由setup()函數完成。在該函數以前的全部代碼都只是預備步驟。爲了構建擴展模塊,須要爲每一個擴展建立一個Extension實例。由於這裏只有一個擴展,因此只需一個Extension實例。
第一個參數是擴展的完整名稱,以及該擴展中擁有的全部高階包。該名稱應該使用完整的點分割表示方式。因爲這裏是個獨立的包,所以名稱爲「Extest」。sources參數是全部源碼文件的列表。一樣,只有一個文件Extest2.c。
如今就能夠調用setup()。其接受一個命名參數來表示構建結果的名稱,以及一個列表來表示須要構建的內容。因爲這裏是建立一個擴展,所以設置一個含有擴展模塊的列表,傳遞給ext_modules。語法以下所示。
因爲這裏只有一個模塊,所以將擴展模塊的實例化代碼集成到setup()的調用中,在預備步驟中將模塊名稱設置爲「常量」MOD。
setup()中含有許多其餘選項,這裏就不一一列舉了。讀者能夠在官方的Python文檔中找到關於建立setup.py和調用setup()的更多信息,在本章末尾能夠找到這些連接。示例8-2顯示了示例擴展中用到的完整腳本。
示例8-2 構建腳本(setup.py)
既然有了setup.py文件,就運行python setup.py build命令構建擴展。這裏在Mac上完成構建(根據操做系統和Python版本的不一樣,對應的輸出與下面的內容會有些差異)。
最後一步是回到Python中使用擴展包,就像這個擴展就是用純Python編寫的那樣。
擴展模塊會建立在build/lib.*目錄下,即運行setup.py腳本的位置。要麼切換到這個目錄中,要麼用下面的方式將其安裝到Python中。
若是安裝該擴展,會獲得下面的輸出。
如今能夠在解釋器中測試模塊了。
須要完成的最後一件事是添加測試函數。實際上,咱們已經有測試函數了,就是那個main()函數。但要當心,在擴展代碼中含有main()函數有潛在的風險,由於系統中應該只有一個main()函數。將main()的名稱改爲test()並對其封裝能夠消除這個風險,添加Extest_test()並更新ExtestMethods數組,以下所示。
Extest_test()模塊函數僅僅運行test()並返回一個空字符串,在Python中是一個None值返回給調用者。
如今能夠在Python中進行相同的測試。
示例8-3中列出了Extest2.c的最終版本,上述輸出都是用這個版原本完成的。
示例8-3 C函數庫的Python封裝版本(Extest2.c)
在這個示例中,僅在同一個文件中將原始的C代碼與Python相關的封裝代碼進行了隔離。這樣方便閱讀,在這個短小的例子中也沒有什麼問題。但在實際應用中,源碼文件會越寫越大,能夠將其分割到不一樣的源碼文件中,使用如ExtestWrappers.c這樣好記的名字。
也許讀者還記得Python使用引用計數來追蹤對象,並釋放再也不引用的對象。這是Python垃圾回收機制的一部分。當建立擴展時,必須額外注意如何處理Python對象,必須留心是否須要修改此類對象的引用計數。
一個對象有兩種類型的引用,一種是擁有引用(owned reference),對該對象的引用計數遞增1表示擁有該對象的全部權。當從零建立一個Python對象時,就必定會含有一個擁有引用。
當使用完一個Python對象後,必須對全部權進行處理,要麼遞減其引用計數,經過傳遞它轉移其全部權,要麼將該對象存儲到其餘容器。若是沒有處理引用計數,則會致使內存泄漏。
對象還有一個借用引用(borrowered reference)。相對來講,這種方式的責任就小一些。通常用於傳遞對象的引用,但不對數據進行任何處理。只要在其引用計數遞減至零後不繼續使用這個引用,就無須擔憂其引用計數。能夠經過遞增對象的引用計數來將借用引用轉成擁有引用。
Python提供了一對C宏來改變Python對象的引用計數。如表8-3所示。
{-:-}表8-3 用於執行Python對象引用計數的宏
函 數
在上面的Extest_test()函數中,在構建PyObject對象時使用空字符串來返回None。但能夠經過擁有一個None對象來完成這個任務。即遞增一個PyNone的引用計數並顯式返回這個對象,以下所示。
Py_INCREF()和 Py_DECREF()還有一個先檢測對象是否爲 NULL 的版本,分別爲Py_XINCREF()和Py_XDECREF()。
這裏強烈建議讀者閱讀相關Python文檔中關於擴展和嵌入Python裏面全部關於引用計數的細節(詳見附錄C中的參考文獻部分)。
擴展的編寫者必需要注意,他們的代碼可能在多線程Python環境中執行。4.3.1節介紹了Python虛擬機(Python Virtual Machine,PVM)和全局解釋器鎖(Global Interpreter Lock,GIL),描述了在PVM中,任意時間只有一個線程在執行,GIL就負責阻止其餘線程的執行。除此以外,還指出了調用外部函數的代碼,如擴展代碼,將會鎖住GIL,直至外部函數返回。
但也提到了一種折衷方法,即讓擴展開發者釋放GIL。例如,在執行系統調用前就能夠實現。這是經過將代碼和線程隔離實現的,這些線程使用了另外的兩個C宏:Py_BEGIN_ALLOW_THREADS和Py_END_ALLOW_THREADS,保證了運行和非運行時的安全性。用這些宏圍起來的代碼塊會容許其餘線程在其執行時同步執行。
與引用計數宏相同,這裏也建議讀者閱讀Python文檔中關於擴展和嵌入Python的內容,以及Python/C API參考手冊。
延伸推薦
點擊關鍵詞閱讀更多新書:
在「異步圖書」後臺回覆「關注」,便可免費得到2000門在線視頻課程;推薦朋友關注根據提示獲取贈書連接,免費得異步圖書一本。趕忙來參加哦!
點擊閱讀原文,查看本書更多信息
掃一掃上方二維碼,回覆「關注」參與活動!