【前 言】
Python是一門強(qiáng)大的編程語言,它很有魅力,同時(shí)也很獨(dú)特,所以掌握起來或許有點(diǎn)兒困難。許多程序員從他們熟悉的語言轉(zhuǎn)到Python之后都打不開思路,所以寫出來的代碼沒有充分發(fā)揮出Python的特性。還有一些程序員則相反,他們會(huì)濫用Python的特性,這樣寫出來的程序以后可能會(huì)出現(xiàn)嚴(yán)重的問題。
本書會(huì)詳細(xì)地告訴大家如何采用符合Python風(fēng)格的方式(Pythonic方式)來編寫程序,這是使用Python語言的最佳方式。筆者假設(shè)你對(duì)這門語言已經(jīng)有了初步了解。編程新手可以通過這本書學(xué)到各種Python功能的最佳用法,有經(jīng)驗(yàn)的程序員則能夠?qū)W會(huì)如何自信地運(yùn)用Python中的新工具。
筆者的目標(biāo)是讓大家用Python開發(fā)出優(yōu)秀的軟件。
【本書涵蓋的內(nèi)容】
本書每一章都包含許多相互關(guān)聯(lián)的條目。大家可以按照自己的需要隨意閱讀這些條目。每個(gè)條目都包含簡(jiǎn)潔而明確的教程,告訴你如何才能更有效率地編寫Python程序。筆者在每個(gè)條目里都給出了建議,告訴大家哪些應(yīng)該做,哪些應(yīng)該避免,以及怎樣在各種做法之間求得平衡,并且會(huì)解釋筆者所選的做法好在哪里。筆者可能會(huì)在某個(gè)條目中提到其他一些條目,讓大家可以全面地了解這些建議所涉及的知識(shí)。
本書第2版只針對(duì)Python 3(參見第1條),這里說的Python 3包含從3.0到3.8的各個(gè)版本。本書第1版中的許多條目仍然收錄在第2版中,并且做了修訂,其中有些條目改動(dòng)比較大。隨著Python語言越來越成熟,最佳編程方法也在演變,所以筆者在第2版中對(duì)某些問題的建議,可能與第1版的完全不同。Python 2已經(jīng)在2020年1月1日退場(chǎng),如果你還是主要在用這個(gè)版本來編程,那么第1版中的建議或許比第2版中的建議更加合適。
Python采用“自帶電池”(batteries included)的理念來設(shè)計(jì)標(biāo)準(zhǔn)庫(kù),不像其他語言那樣只提供少數(shù)幾個(gè)常用的軟件包,如果需要的重要功能不在這些軟件包里,那就要自己去尋找了。許多Python內(nèi)置軟件包與Python的習(xí)慣用法有著密切的關(guān)系,所以實(shí)際上已經(jīng)成了語言規(guī)范的一部分。本書篇幅有限,不可能把所有的標(biāo)準(zhǔn)模塊全都講一遍,但會(huì)涵蓋其中那些需要了解和使用且用法比較關(guān)鍵的模塊。
●第1章:培養(yǎng) Pythonic 思維
Python開發(fā)者社區(qū)用Pythonic這個(gè)形容詞來描述具有某種特定風(fēng)格的代碼。這種風(fēng)格是大家在使用Python語言編程并相互協(xié)作的過程中逐漸形成的。本章講解如何采用這樣的風(fēng)格編寫常見的Python代碼。
●第2章:列表與字典
在Python語言中整理信息時(shí),最常用的方法是把一系列數(shù)值保存到列表(list)中。既然有列表,那就有另外一種跟它互補(bǔ)的結(jié)構(gòu),也就是字典(dict),這種結(jié)構(gòu)可以把它存儲(chǔ)的查找鍵映射到對(duì)應(yīng)的值上。本章講解如何采用這些數(shù)據(jù)結(jié)構(gòu)來編寫程序。
●第3章:函數(shù)
Python中的函數(shù)具備多種特性,這有助于簡(jiǎn)化編程工作。Python函數(shù)的某些性質(zhì)與其他編程語言中函數(shù)的類似,但也有一些是Python獨(dú)有的。本章介紹如何使用函數(shù)來表達(dá)開發(fā)者的意圖,如何讓代碼更容易復(fù)用,以及如何減少bug。
●第4章:推導(dǎo)與生成
Python有一種特殊的語法,可以迅速迭代列表(list)、字典(dict)與集合(set),并據(jù)此生成相應(yīng)的數(shù)據(jù)結(jié)構(gòu),這讓我們能夠在函數(shù)返回的這種結(jié)構(gòu)上逐個(gè)訪問根據(jù)原結(jié)構(gòu)所派生出來的一系列值。本章講解怎樣利用這種機(jī)制來提升程序效率并降低內(nèi)存用量,同時(shí)讓代碼變得更容易讀懂。
●第5章:類與接口
Python是面向?qū)ο蟮恼Z言。用Python編程時(shí),經(jīng)常要編寫新的類,而且還要定義這些類應(yīng)該如何通過其接口以及繼承體系與其他代碼相交互。本章講解怎樣使用類來表達(dá)對(duì)象所應(yīng)具備的行為。
●第 6 章:元類與屬性
元類(metaclass)與動(dòng)態(tài)屬性(dynamic attribute)都是很強(qiáng)大的Python特性,但它們也有可能會(huì)讓程序出現(xiàn)古怪的行為與意外的效果。本章講解這些機(jī)制的習(xí)慣用法,確保讀者寫出來的代碼遵循最小驚訝原則(rule of least surprise)。
●第7章:并發(fā)與并行
用Python很容易寫并發(fā)程序,這種程序可以在同一時(shí)刻做許多件不同的事情。Python也可以通過系統(tǒng)調(diào)用、子進(jìn)程以及C語言擴(kuò)展來實(shí)現(xiàn)并行處理。本章介紹這些Python特性應(yīng)該用在什么情況下。
●第8章:穩(wěn)定與性能
Python內(nèi)置了一些功能與模塊,可以讓程序變得更加可靠。另外,Python還提供了一些工具,可以讓我們輕松地提升程序的性能。本章講解怎樣用Python優(yōu)化程序,讓這些程序在正式的運(yùn)行環(huán)境中表現(xiàn)得更加穩(wěn)定、更加高效。
●第 9 章:測(cè)試與調(diào)試
不管使用哪種語言編程,我們都應(yīng)該把寫出來的代碼測(cè)試一下。但對(duì)于Python來說,還有個(gè)特殊的問題,那就是它所提供的動(dòng)態(tài)機(jī)制可能會(huì)增加程序在運(yùn)行時(shí)出現(xiàn)錯(cuò)誤的風(fēng)險(xiǎn)。好在Python也讓我們可以比較容易地編寫測(cè)試代碼和故障診斷程序。本章講解怎樣用Python內(nèi)置的工具來測(cè)試并調(diào)試程序。
●第 10 章:協(xié)作開發(fā)
如果許多人要協(xié)作開發(fā)一個(gè)Python程序,那就得仔細(xì)商量代碼的寫法了。即便你只是一個(gè)人開發(fā),也需要了解如何使用其他人所寫的模塊。本章介紹標(biāo)準(zhǔn)的工具以及業(yè)界總結(jié)出來的最佳方法,告訴大家怎樣協(xié)作開發(fā)Python程序。
【獲取源代碼及勘誤表】
微信關(guān)注“華章計(jì)算機(jī)”,回復(fù)“67402”,可免費(fèi)獲取本書源代碼下載地址,并加入讀者交流群。拿到范例代碼之后,可以拋開書中講解代碼的那些內(nèi)容,單獨(dú)把這個(gè)完整的程序運(yùn)行一遍,你可以用這些代碼做實(shí)驗(yàn),并試著理解程序?yàn)槭裁磿?huì)這樣運(yùn)行。
●第1章 培養(yǎng)Pythonic思維 // 1
第1條 查詢自己使用的Python版本 // 1
第2條 遵循PEP 8風(fēng)格指南 // 2
第3條 了解bytes與str的區(qū)別 // 5
第4條 用支持插值的f-string取代C風(fēng)格的格式字符串與str.format方法 // 9
第5條 用輔助函數(shù)取代復(fù)雜的表達(dá)式 // 19
第6條 把數(shù)據(jù)結(jié)構(gòu)直接拆分到多個(gè)變量里,不要專門通過下標(biāo)訪問 // 21
第7條 盡量用enumerate取代range // 25
第8條 用zip函數(shù)同時(shí)遍歷兩個(gè)迭代器 // 26
第9條 不要在for與while循環(huán)后面寫else塊 // 28
第10條 用賦值表達(dá)式減少重復(fù)代碼 // 31
●第2章 列表與字典 // 37
第11條 學(xué)會(huì)對(duì)序列做切片 // 37
第12條 不要在切片里同時(shí)指定起止下標(biāo)與步進(jìn) // 40
第13條 通過帶星號(hào)的unpacking操作來捕獲多個(gè)元素,不要用切片 // 42
第14條 用sort方法的key參數(shù)來表示復(fù)雜的排序邏輯 // 45
第15條 不要過分依賴給字典添加條目時(shí)所用的順序 // 51
第16條 用get處理鍵不在字典中的情況,不要使用in與KeyError // 56
第17條 用defaultdict處理內(nèi)部狀態(tài)中缺失的元素,而不要用setdefault // 61
第18條 學(xué)會(huì)利用__missing__構(gòu)造依賴鍵的默認(rèn)值 // 63
●第3章 函數(shù) // 66
第19條 不要把函數(shù)返回的多個(gè)數(shù)值拆分到三個(gè)以上的變量中 // 66
第20條 遇到意外狀況時(shí)應(yīng)該拋出異常,不要返回None // 69
第21條 了解如何在閉包里面使用外圍作用域中的變量 // 71
第22條 用數(shù)量可變的位置參數(shù)給函數(shù)設(shè)計(jì)清晰的參數(shù)列表 // 75
第23條 用關(guān)鍵字參數(shù)來表示可選的行為 // 77
第24條 用None和docstring來描述默認(rèn)值會(huì)變的參數(shù) // 80
第25條 用只能以關(guān)鍵字指定和只能按位置傳入的參數(shù)來設(shè)計(jì)清晰的參數(shù)列表 // 83
第26條 用functools.wraps定義函數(shù)修飾器 // 88
●第4章 推導(dǎo)與生成 // 91
第27條 用列表推導(dǎo)取代map與filter // 91
第28條 控制推導(dǎo)邏輯的子表達(dá)式不要超過兩個(gè) // 93
第29條 用賦值表達(dá)式消除推導(dǎo)中的重復(fù)代碼 // 94
第30條 不要讓函數(shù)直接返回列表,應(yīng)該讓它逐個(gè)生成列表里的值 // 97
第31條 謹(jǐn)慎地迭代函數(shù)所收到的參數(shù) // 100
第32條 考慮用生成器表達(dá)式改寫數(shù)據(jù)量較大的列表推導(dǎo) // 104
第33條 通過yield from把多個(gè)生成器連起來用 // 106
第34條 不要用send給生成器注入數(shù)據(jù) // 108
第35條 不要通過throw變換生成器的狀態(tài) // 113
第36條 考慮用itertools拼裝迭代器與生成器 // 117
●第5章 類與接口 // 122
第37條 用組合起來的類來實(shí)現(xiàn)多層結(jié)構(gòu),不要用嵌套的內(nèi)置類型 // 122
第38條 讓簡(jiǎn)單的接口接受函數(shù),而不是類的實(shí)例 // 128
第39條 通過@classmethod多態(tài)來構(gòu)造同一體系中的各類對(duì)象 // 131
第40條 通過super初始化超類 // 136
第41條 考慮用mix-in類來表示可組合的功能 // 140
第42條 優(yōu)先考慮用public屬性表示應(yīng)受保護(hù)的數(shù)據(jù),不要用private屬性表示 // 144
第43條 自定義的容器類型應(yīng)該從collections.abc繼承 // 149
●第6章 元類與屬性 // 153
第44條 用純屬性與修飾器取代舊式的setter與getter方法 // 153
第45條 考慮用@property實(shí)現(xiàn)新的屬性訪問邏輯,不要急著重構(gòu)原有的代碼 // 157
第46條 用描述符來改寫需要復(fù)用的@property方法 // 160
第47條 針對(duì)惰性屬性使用__getattr__、__getattribute__及__setattr__ // 165
第48條 用__init_subclass__驗(yàn)證子類寫得是否正確 // 170
第49條 用__init_subclass__記錄現(xiàn)有的子類 // 177
第50條 用__set_name__給類屬性加注解 // 181
第51條 優(yōu)先考慮通過類修飾器來提供可組合的擴(kuò)充功能,不要使用元類 // 185
●第7章 并發(fā)與并行 // 191
第52條 用subprocess管理子進(jìn)程 // 192
第53條 可以用線程執(zhí)行阻塞式I/O,但不要用它做并行計(jì)算 // 195
第54條 利用Lock防止多個(gè)線程爭(zhēng)用同一份數(shù)據(jù) // 199
第55條 用Queue來協(xié)調(diào)各線程之間的工作進(jìn)度 // 202
第56條 學(xué)會(huì)判斷什么場(chǎng)合必須做并發(fā) // 210
第57條 不要在每次fan-out時(shí)都新建一批Thread實(shí)例 // 214
第58條 學(xué)會(huì)正確地重構(gòu)代碼,以便用Queue做并發(fā) // 218
第59條 如果必須用線程做并發(fā),那就考慮通過ThreadPoolExecutor實(shí)現(xiàn) // 224
第60條 用協(xié)程實(shí)現(xiàn)高并發(fā)的I/O // 226
第61條 學(xué)會(huì)用asyncio改寫那些通過線程實(shí)現(xiàn)的I/O // 230
第62條 結(jié)合線程與協(xié)程,將代碼順利遷移到asyncio // 239
第63條 讓asyncio的事件循環(huán)保持暢通,以便進(jìn)一步提升程序的響應(yīng)能力 // 245
第64條 考慮用concurrent.futures實(shí)現(xiàn)真正的并行計(jì)算 // 248
●第8章 穩(wěn)定與性能 // 253
第65條 合理利用try/except/else/finally結(jié)構(gòu)中的每個(gè)代碼塊 // 253
第66條 考慮用contextlib和with語句來改寫可復(fù)用的try/finally代碼 // 258
第67條 用datetime模塊處理本地時(shí)間,不要用time模塊 // 262
第68條 用copyreg實(shí)現(xiàn)可靠的pickle操作 // 265
第69條 在需要準(zhǔn)確計(jì)算的場(chǎng)合,用decimal表示相應(yīng)的數(shù)值 // 272
第70條 先分析性能,然后再優(yōu)化 // 274
第71條 優(yōu)先考慮用deque實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者隊(duì)列 // 278
第72條 考慮用bisect搜索已排序的序列 // 284
第73條 學(xué)會(huì)使用heapq制作優(yōu)先級(jí)隊(duì)列 // 286
第74條 考慮用memoryview與bytearray來實(shí)現(xiàn)無須拷貝的bytes操作 // 294
●第9章 測(cè)試與調(diào)試 // 300
第75條 通過repr字符串輸出調(diào)試信息 // 301
第76條 在TestCase子類里驗(yàn)證相關(guān)的行為 // 304
第77條 把測(cè)試前、后的準(zhǔn)備與清理邏輯寫在setUp、tearDown、setUpModule與tearDownModule中,以防用例之間互相干擾 // 309
第78條 用Mock來模擬受測(cè)代碼所依賴的復(fù)雜函數(shù) // 312
第79條 把受測(cè)代碼所依賴的系統(tǒng)封裝起來,以便于模擬和測(cè)試 // 319
第80條 考慮用pdb做交互調(diào)試 // 322
第81條 用tracemalloc來掌握內(nèi)存的使用與泄漏情況 // 326
●第10章 協(xié)作開發(fā) // 329
第82條 學(xué)會(huì)尋找由其他Python開發(fā)者所構(gòu)建的模塊 // 329
第83條 用虛擬環(huán)境隔離項(xiàng)目,并重建依賴關(guān)系 // 330
第84條 每一個(gè)函數(shù)、類與模塊都要寫docstring // 335
第85條 用包來安排模塊,以提供穩(wěn)固的API // 339
第86條 考慮用模塊級(jí)別的代碼配置不同的部署環(huán)境 // 344
第87條 為自編的模塊定義根異常,讓調(diào)用者能夠?qū)iT處理與此API有關(guān)的異!// 346
第88條 用適當(dāng)?shù)姆绞酱蚱蒲h(huán)依賴關(guān)系 // 350
第89條 重構(gòu)時(shí)考慮通過warnings提醒開發(fā)者API已經(jīng)發(fā)生變化 // 355
第90條 考慮通過typing做靜態(tài)分析,以消除bug // 361