1. 程式人生 > >編寫高質量程式碼改善Python程式的很多建議

編寫高質量程式碼改善Python程式的很多建議

基礎語法

有節制地使用 from...import 語句

Python 提供三種方式來引入外部模組:import語句、from...import語句以及__import__函式,其中__import__函式顯式地將模組的名稱作為字串傳遞並賦值給名稱空間的變數。

使用import需要注意以下幾點:

  • 優先使用import a的形式

  • 有節制地使用from a import A

  • 儘量避免使用from a import *

為什麼呢?我們來看看 Python 的 import 機制,Python 在初始化執行環境的時候會預先載入一批內建模組到記憶體中,同時將相關資訊存放在sys.modules中,我們可以通過sys.modules.items()檢視預載入的模組資訊,當載入一個模組時,直譯器實際上完成了如下動作:

  1. 在sys.modules中搜索該模組是否存在,如果存在就匯入到當前區域性名稱空間,如果不存在就為其建立一個字典物件,插入到sys.modules中

  2. 載入前確認是否需要對模組對應的檔案進行編譯,如果需要則先進行編譯

  3. 執行動態載入,在當前名稱空間中執行編譯後的位元組碼,並將其中所有的物件放入模組對應的字典中

編寫高質量程式碼改善Python程式的很多建議

從上可以看出,對於使用者自定義的模組,import 機制會建立一個新的 module 將其加入當前的區域性名稱空間中,同時在 sys.modules 也加入該模組的資訊,但本質上是在引用同一個物件,通過test.py所在的目錄會多一個位元組碼檔案。

優先使用 absolute import 來匯入模組

i+=1 不等於 ++i

首先++i或--i在 Python 語法上是合法,但並不是我們通常理解的自增或自減操作:

編寫高質量程式碼改善Python程式的很多建議

原來+或-只表示正負數符號。

使用 with 自動關閉資源

對於開啟的資源我們記得關閉它,如檔案、資料庫連線等,Python 提供了一種簡單優雅的解決方案:with。

先來看with實現的原理吧。

with的實現得益於一個稱為上下文管理器(context manager)的東西,它定義程式執行時需要建立的上下文,處理程式的進入和退出,實現了上下文管理協議,即物件中定義了__enter__()和__exit__(),任何實現了上下文協議的物件都可以稱為一個上下文管理器:

  • __enter__():返回執行時上下文相關的物件

  • __exit__(exception_type, exception_value, traceback):退出執行時的上下文,處理異常、清理現場等

with 表示式 [as 目標]:程式碼塊

包含with語句的程式碼塊執行過程如下:

編寫高質量程式碼改善Python程式的很多建議

  1. 計算表示式的值,返回一個上下文管理器物件

  2. 載入上下文管理器物件的__exit__()以備後用

  3. 呼叫上下文管理器物件的__enter__()

  4. 將__enter__()的返回值賦給目標物件

  5. 執行程式碼塊,正常結束呼叫__exit__(),其返回值直接忽略,如果發生異常,會呼叫__exit__()並將異常型別、值及 traceback 作為引數傳遞給__exit__(),__exit__()返回值為 false 異常將會重新丟擲,返回值為 true 異常將被掛起,程式繼續執行

於此,我們可以自定義一個上下文管理器:

編寫高質量程式碼改善Python程式的很多建議

Python 還提供contextlib模組,通過 Generator 實現,其中的 contextmanager 作為裝飾器來提供一種針對函式級別上的上下文管理器,可以直接作用於函式/物件而不必關心__enter__()和__exit__()的實現。

使用 else 子句簡化迴圈(異常處理)

Python 的 else 子句提供了隱含的對迴圈是否由 break 語句引發迴圈結束的判斷,有點繞哈,來看例子:

編寫高質量程式碼改善Python程式的很多建議

可以看出,else 子句在迴圈正常結束和迴圈條件不成立時被執行,由 break 語句中斷時不執行,同樣,我們可以利用這顆語法糖作用在 while 和 try...except 中。

遵循異常處理的幾點基本原則

異常處理的幾點原則:

  1. 注意異常的粒度,不推薦在 try 中放入過多的程式碼

  2. 謹慎使用單獨的 except 語句處理所有異常,最好能定位具體的異常

  3. 注意異常捕獲的順序,在適合的層次處理異常,Python 是按內建異常類的繼承結構處理異常的,所以推薦的做法是將繼承結構中子類異常在前丟擲,父類異常在後丟擲

  4. 使用更為友好的異常資訊,遵守異常引數的規範

避免 finally 中可能發生的陷阱

當 finally 執行完畢時,之前臨時儲存的異常將會再次被丟擲,但如果 finally 語句中產生了新的異常或執行了 return 或 break 語句,那麼臨時儲存的異常將會被丟失,從而異常被遮蔽。

在實際開發中不推薦 finally 中使用 return 語句進行返回。

深入理解 None,正確判斷物件是否為空

型別FalseTrue布林False (與0等價)True (與1等價)字串""( 空字串)非空字串,例如 " ", "blog"數值0, 0.0非0的數值,例如:1, 0.1, -1, 2容器[], (), {}, set()至少有一個元素的容器物件,例如:[0], (None,), ['']NoneNone非None物件

編寫高質量程式碼改善Python程式的很多建議

#3執行中會呼叫__nonzero__()來判斷自身物件是否為空並返回0/1或True/False,如果沒有定義該方法,Python 將呼叫__len__()進行判斷,返回 0 表示為空。如果一個類既沒有定義__len__()又沒有定義__nonzero__(),該類例項用 if 判斷為True。

連線字串優先使用 join 而不是 +

這一點之前我在博文裡總結過,+涉及到更多的記憶體操作。

格式化字串時儘量使用 .format 而不是 %

同上。

區別對待可變物件和不可變物件

Python 中一切皆物件,每個物件都有一個唯一的識別符號(id)、型別(type)和值。數字、字串、元組屬於不可變物件,字典、列表、位元組陣列屬於可變物件。

編寫高質量程式碼改善Python程式的很多建議

預設引數在初始化時僅僅被評估一次,以後直接使用第一次評估的結果,course 指向的是 list 的地址,每次操作的實際上是 list 所指向的具體列表,所以對於可變物件的更改會直接影響原物件。

最好的方法是傳入None作為預設引數,在建立物件的時候動態生成列表。

編寫高質量程式碼改善Python程式的很多建議

[]、() 和 {} 一致的容器初始化形式

編寫高質量程式碼改善Python程式的很多建議

其實就是列表生成式、元組生成式和字典生成式。

記住函式傳參既不是傳值也不是傳引用

正確的說法是傳物件(call by object)或傳物件的引用(call-by-object-reference),函式引數在傳遞過程中將整個物件傳入,對可變物件的修改在函式外部以及內部都可見,對不可變物件的”修改“往往是通過生成一個新物件然是賦值實現的。

警惕預設引數潛在的問題

其中就是預設引數如果是可變物件,在呼叫者和被呼叫者之間是共享的。

編寫高質量程式碼改善Python程式的很多建議

慎用變長引數

  • 原因如下:

  1. 使用過於靈活,導致函式簽名不夠清晰,存在多種呼叫方式

  2. 使用*args和**kw簡化函式定義就意味著函式可以有更好的實現方法

  • 使用場景:

  1. 為函式新增一個裝飾器

  2. 引數數目不確定

  3. 實現函式的多型或子類需要呼叫父類的某些方法時

深入理解 str() 和repr() 的區別

  • 總結幾點:

  1. str()面向使用者,返回使用者友好和可讀性強的字串型別;repr()面向 Python 直譯器或開發人員,返回 Python 直譯器內部的含義

  2. 直譯器中輸入a預設呼叫repr(),而print(a)預設呼叫str()

  3. repr()返回值一般可以用eval()還原物件:obj == eval(repr(obj))

  4. 以上兩個方法分別呼叫內建的__str__()和__repr__(),一般來說類中都應該定義__repr__(),但當可讀性比準確性更為重要時應該考慮__str__(),使用者實現__repr__()方法的時候最好保證其返回值可以用eval()是物件還原

分清 staticmethod 和 classmethod 的適用場景

這兩種方法之前已經總結過了的,下面我們只討論它們的使用場景。

呼叫類方法裝飾器的修飾器的方法,會隱式地傳入該物件所對應的類,可以動態生成對應的類的類變數,同時如果我們期望根據不同的型別返回對應的類的例項,類方法才是正確的解決方案。

反觀靜態方法,當我們所定義的方法既不跟特定的例項相關也不跟特定的類相關,可以將其定義為靜態方法,這樣使我們的程式碼能夠有效地組織起來,提高可維護性。

當然,也可以考慮定義一個模組,將一組的方法放入其中,通過模組來訪問。