《改善python程式的91個建議》讀書筆記

金色旭光發表於2020-12-29

推薦

《改善Pthon程式的91個建議》是從基本原則、慣用方法、語法、庫、設計模式、內部機制、開發工具和效能優化8個方面深入探討編寫高質量python程式碼的技巧、禁忌和最佳實踐。
讀書就如同和作者對話,閱讀本書可以感覺的到作者是一個實戰經驗豐富的Pythoner,與高手對話受益匪淺。對於一個想要提高自己python程式碼的coder來說值得一讀的。
邊讀書邊記錄,把我認為重要的建議記錄下來,20個左右,希望更多人能從中獲益。

引論

建議5:通過適當新增空行使程式碼佈局更為優雅合理

  1. 在函式定義或者類定義之間空兩行,類定義與第一個方法之間空一行。pep8規範
  2. 函式呼叫時,呼叫者在上,被呼叫者在下
  3. 空格使用:
    • 在二元運算 =, 比較 ==, <, >, !=, in, not in, is, is not, 布林運算 and or not 的左右兩邊應該有空格
    • 逗號和分號前不需要使用空格如:推薦:x, y == y, x 不推薦:x , y = y , x

建議6:編寫函式的4個原則

  1. 函式設計要儘量短小,巢狀層次不宜過深
  2. 函式申明應該做到合理,簡單,易於使用。函式名合理,引數不宜過多。
  3. 函式引數設計應該考慮向下相容。通過新增預設引數來實現。
  4. 一個函式只做一件事,儘量保證函式語句粒度的一致性。

建議7: 將常量集中到一個檔案

  1. 通過命名風格來題型使用者該變數代表的意義為常量。如常量名為所有字母大寫
  2. 通過自定義的類實現常量功能。實現命名全部大寫值一旦繫結便不可再修改這兩個條件

程式設計慣用法

建議9:資料交換值的時候不推薦使用中間變數

使用x, y = y, x的方式交換變數值比使用中間值更加高效
該方式本質上是使用了元組的打包功能

建議10: 充分利用 Lazy evaluation 的特性

Lazy evaluation 就是延遲計算或者惰性計算, 指僅僅在真正需要執行的時候才計算表示式的值。優點有如下:

  1. 避免不必要的計算,帶來效能上的提升
  2. 節省空間,使得無限迴圈的資料結構成為可能。

建議16: 分清 == 與 is的使用場景

is 的作用是比較兩個物件在記憶體中是否擁有同一塊記憶體空間,它並不適合用來判斷兩個字串是否相等。
== 用來檢查兩個物件的值是否相等

基礎語法

建議22:使用with自動關閉資源

在檔案操作、執行緒用鎖等情況下,優先使用 with 替代 try catch。with語句使用更加簡潔

建議24:遵循異常處理的幾點基本原則

  1. 注意異常的粒度,不推薦在try中放入過多的程式碼。
  2. 謹慎使用單獨的except語句處理所有異常,最好能定位具體的異常
  3. 注意異常捕獲的順序,在合適的層次處理異常
  4. 使用更為友好的異常資訊,遵守異常引數的規範。

建議25:避免finally中可能發生的陷阱

當try塊中發生異常的時候,如果在except語句中找不到對應的異常處理,異常處理會被臨時儲存起來,當finally執行完畢的時候,臨時儲存的異常將會再次被丟擲,但如果finally語句中產生了新的異常或者執行了return或者break語句,那麼臨時儲存的異常就會丟失,從而導致異常遮蔽。

不推薦在finally中使用return語句進行返回

建議27:連線字串應優先使用join而不是+

join的效率更高。join是將最後的結果算好之後,申請記憶體,一次完成
+是不斷開闢新的空間,將原理的字串和現在的一起搬到空間中。效率會低很多

建議28:格式化字串時儘量使用format而不是%

  1. format方式在使用較%操作符更加靈活
  2. format可以方便地作為引數傳遞
  3. 官方文件宣稱 % 最終會被 format替代
  4. %方法在某些特殊情況下使用時需要特別小心

建議32:警惕預設引數潛在的問題

多個函式呼叫時,使用同一個預設引數,如果引數是列表,會導致多個函式公用一個列表

建議39:使用Counter進行計數統計

技術統計,統計某一項出現的次數。可以使用不同的資料結構來實現。

  1. 使用dict
  2. 使用defaultdict
  3. 使用set 和list

優雅的方式:
使用collections包中的Counter,這是python自帶模組,用來統計容器中個元素出現的次數。

使用工具輔助專案開發

建議76:使用Pylint檢查程式碼風格

Pylint始於2003年,是一個程式碼分析工具,用於檢查python程式碼中的錯誤,查詢不符合程式碼編碼規範的程式碼以及潛在的問題。支援不同的OS平臺,如windows、linux、osx等。其特性如下:

  1. 程式碼風格審查。以Guido van Rossum的PEP8為標準
  2. 程式碼錯誤檢查。未被實現的介面,方法缺少對應的引數
  3. 發現重複已經設計不合理的程式碼
  4. 高度的可配置化和可定製化
  5. 支援各種IDE和編輯器整合
  6. 能夠基於python程式碼生成UML圖
  7. 能夠與Hudson、Jenkins等持續整合工具相結合支援自動程式碼評審

效能剖析與優化

建議84:掌握迴圈優化的基本技巧

  1. 減少迴圈內部的計算
  2. 使用計算表示式替換迴圈
  3. 迴圈中儘量引用區域性變數:名稱空間中區域性變數優先搜尋
  4. 關注內層巢狀迴圈。在多層巢狀迴圈中,重點關注內層巢狀迴圈。

建議85:使用生成器提高效率

  1. 生成器提供了一種更為便利的產生迭代器的方式,使用者一般不需要自己實現__iter__和next方法,它預設返回一個迭代器
  2. 程式碼更為簡介,優雅
  3. 充分利用了延遲評估的特性,僅在需要的時候才產生對應的元素,而不是一次生成所有的元素。從而節省了記憶體空間,提高了效率,理論上無限迴圈成為可能,而不會導致MenoryError。在大資料處理的情況下尤為重要
  4. 使得協同程式更加容易實現。

建議86:使用不同的資料結構優化效能

  1. list物件經常有數量的鉅變,膨脹和收縮很頻繁,那麼應當考慮使用deque
  2. 在使用list的過程中,需要時刻保持列表的有序性,可以使用標準庫bisect實現
  3. heapq模組,將一個序列容器轉化程一個堆

建議87 充分利用set的優勢

set是通過Hash演算法實現的無序不重複的元素集。

使用set的場景:

  1. 涉及到求list交集、並集或者差集問題可以轉換程set操作
  2. 在對list頻繁查詢的情況,也可以換成set

建議89:使用執行緒池提高效率

執行緒的生命週期分為5個狀態:建立、就緒、執行、阻塞、終止。自執行緒建立到終止,執行緒便不斷在執行、就緒、阻塞這三個狀態之間轉換直銷燬。真正佔有CPU的只有執行、建立、銷燬這仨個狀態。

一個執行緒的執行時間可以由此分為三個部分:

  • 執行緒的啟動時間
  • 執行緒的執行時間
  • 執行緒的銷燬時間

在多執行緒處理的情景中,如果執行緒不能夠被重用,就意味著每次建立都需要經過啟動、銷燬、執行這三個過程。這必然會增加系統的相應時間,降低效率。如何提高執行緒執行的效率呢?執行緒池
實現建立多個能夠執行任務的執行緒放入執行緒池,所需要執行的任務通常被安排在佇列中。通常情況下,需要處理的任務比執行緒數目要多,執行緒執行完當前任務後,會從佇列中取下一個任務,直到所有的任務已經完成。

由於執行緒池預先被建立並放入執行緒池中,同時處理完當前任務之後並不是銷燬而是被安排處理下一個任務,因此能夠避免多次建立執行緒,從而節省執行緒建立和銷燬的開銷,帶來更好的效能和系統穩定性。

執行緒池技術適合處理突發性大量請求或者需要大量執行緒來完成任務,但任務實際處理時間較短的應用場景,它能有效避免由於系統中建立執行緒過多而導致的系統效能負荷過大,響應過慢等問題。

建議90: 使用C/C++模組擴充套件提高效能

建議91: 使用Cython編寫擴充套件模組

附錄完整91個建議

1:引論
建議1、理解Pythonic概念—-詳見Python中的《Python之禪》
建議2、編寫Pythonic程式碼
建議3:理解Python與C的不同之處,比如縮排與{},單引號雙引號,三元操作符?,Switch-Case語句等。
建議4:在程式碼中適當新增註釋
建議5:適當新增空行使程式碼佈局更加合理
建議6:編寫函式的4個原則
建議7:將常量集中在一個檔案,且常量名儘量使用全大寫字母

2:程式設計慣用法
建議8:利用assert語句來發現問題,但要注意,斷言assert會影響效率
建議9:資料交換值時不推薦使用臨時變數,而是直接a, b = b, a
建議10:充分利用惰性計算(Lazy evaluation)的特性,從而避免不必要的計算
建議11:理解列舉替代實現的缺陷(最新版Python中已經加入了列舉特性)
建議12:不推薦使用type來進行型別檢查,因為有些時候type的結果並不一定可靠。如果有需求,建議使用isinstance函式來代替
建議13:儘量將變數轉化為浮點型別後再做除法(Python3以後不用考慮)
建議14:警惕eval()函式的安全漏洞,有點類似於SQL隱碼攻擊
建議15:使用enumerate()同時獲取序列迭代的索引和值
建議16:分清==和is的適用場景,特別是在比較字串等不可變型別變數時(詳見評論)
建議17:儘量使用Unicode。在Python2中編碼是很讓人頭痛的一件事,但Python3就不用過多考慮了
建議18:構建合理的包層次來管理Module

3:基礎用法
建議19:有節制的使用from…import語句,防止汙染名稱空間
建議20:優先使用absolute import來匯入模組(Python3中已經移除了relative import)
建議21:i+=1不等於++i,在Python中,++i前邊的加號僅表示正,不表示操作
建議22:習慣使用with自動關閉資源,特別是在檔案讀寫中
建議23:使用else子句簡化迴圈(異常處理)
建議24:遵循異常處理的幾點基本原則
建議25:避免finally中可能發生的陷阱
建議26:深入理解None,正確判斷物件是否為空。Python中下列資料會判斷為空:
建議27:連線字串應優先使用join函式,而不是+操作
建議28:格式化字串時儘量使用.format函式,而不是%形式
建議29:區別對待可變物件和不可變物件,特別是作為函式引數時
建議30:[], {}和():一致的容器初始化形式。使用列表解析可以使程式碼更清晰,同時效率更高
建議31:函式傳引數,既不是傳值也不是傳引用,而是傳物件或者說物件的引用
建議32:警惕預設引數潛在的問題,特別是當預設引數為可變物件時
建議33:函式中慎用變長引數*args和**kargs
建議34:深入理解str()和repr()的區別
建議35:分清靜態方法staticmethod和類方法classmethod的使用場景

4:庫
建議36:掌握字串的基本用法
建議37:按需選擇sort()和sorted()函式
建議38:使用copy模組深拷貝物件,區分淺拷貝(shallow copy)和深拷貝(deep copy)
建議39:使用Counter進行計數統計,Counter是字典類的子類,在collections模組中
建議40:深入掌握ConfigParse
建議41:使用argparse模組處理命令列引數
建議42:使用pandas處理大型CSV檔案
建議43:使用ElementTree解析XML
建議44:理解模組pickle的優劣
建議45:序列化的另一個選擇JSON模組:load和dump操作
建議46:使用traceback獲取棧資訊
建議47:使用logging記錄日誌資訊
建議48:使用threading模組編寫多執行緒程式
建議49:使用Queue模組使多執行緒程式設計更安全

5:設計模式
建議50:利用模組實現單例模式
建議51:用mixin模式讓程式更加靈活
建議52:用釋出-訂閱模式實現鬆耦合
建議53:用狀態模式美化程式碼

6:內部機制
建議54:理解build-in物件
建議55:__init__()不是構造方法,理解__new__()與它之間的區別
建議56:理解變數的查詢機制,即作用域
建議57:為什麼需要self引數
建議58:理解MRO(方法解析順序)與多繼承
建議59:理解描述符機制
建議60:區別__getattr__()與__getattribute__()方法之間的區別
建議61:使用更安全的property
建議62:掌握元類metaclass
建議63:熟悉Python物件協議
建議64:利用操作符過載實現中綴語法
建議65:熟悉Python的迭代器協議
建議66:熟悉Python的生成器
建議67:基於生成器的協程和greenlet,理解協程、多執行緒、多程式之間的區別
建議68:理解GIL的侷限性
建議69:物件的管理和垃圾回收

7:使用工具輔助專案開發
建議70:從PyPI安裝第三方包
建議71:使用pip和yolk安裝、管理包
建議72:做paster建立包
建議73:理解單元測試的概念
建議74:為包編寫單元測試
建議75:利用測試驅動開發(TDD)提高程式碼的可測性
建議76:使用Pylint檢查程式碼風格
建議77:進行高效的程式碼審查
建議78:將包釋出到PyPI

8:效能剖析與優化
建議79:瞭解程式碼優化的基本原則
建議80:藉助效能優化工具
建議81:利用cProfile定位效能瓶頸
建議82:使用memory_profiler和objgraph剖析記憶體使用
建議83:努力降低演算法複雜度
建議84:掌握迴圈優化的基本技巧
建議85:使用生成器提高效率
建議86:使用不同的資料結構優化效能
建議87:充分利用set的優勢
建議88:使用multiprocessing模組克服GIL缺陷
建議89:使用執行緒池提高效率
建議90:使用C/C++模組擴充套件提高效能
建議91:使用Cythonb編寫擴充套件模組

相關文章