Skip to content

Fluent Python 讀書筆記(八)

  • Python

類別中繼編程

  • 「中繼類別是比 99% 使用者鎖想的還要艱深的魔法。如果你想知道自己是否須要他們,其實你不需要。」
  • 類別中繼邊程是在執行階段建立/自訂類別的技巧
  • 類別是一級物件,無論什麼時候,都可以用函式來建立新的類別(諸如類別修飾器),不需要 class 關鍵字
  • 中繼類別很強大,但很難正確使用,事實上,很難在實際的程式中使用
  • 編寫中繼程式的先決條件是了解匯入階段與執行階段的差異
  • 「如果你不在製作框架,就不該編寫中繼類別」

自訂一個類似 collections.namedtuple 的簡易紀錄型類別工廠

對這種紀錄型的類別而言,我們希望屬性群都是相同的,而且擁有相同順序

此範例建立的類別有一個限制:無法被序列化,即無法與 picle 模組的 dumpload 函式一起使用

在類別中定義 __slots__,等於告訴解譯器「它們都是這個類別的實例屬性」,這些屬性會被存在一個類 tuple 結構,避免每一個實例的 __dict__ 產生記憶體開銷

當類別指定 __slots__ 時,它的實例無法使用任何指定之外的屬性(這是一種副作用/缺點),單純為了做這種屬性限制而使用 __slots__ 並不是一個好的作法。__slots__ 的目標是最佳化,而不是限制開發者的行為


  • 我們通常會把 type 當成函式來用,但它同時是個類別,如果你用三個引數來呼叫,它的行為就像類別,會實體化一個新的類別
  • 避免使用 execeval 來編寫中繼程式是一種好的習慣。如果不受信任的來源傳送字串/段落給這些函式,會造成嚴重的後果;Python 提供足夠的自我檢查工具,execeval 在大部分情況下沒必要使用
  • 類別描述器有一個重大的缺點在於,它們只能在直接套用的類別上動作——被修飾類別的子類別可能不會繼承修飾器所作的改變
  • 「匯入階段(import time)」、「執行階段(runtime)」這些用語沒有經過嚴謹定義,而且它們之間有灰色地帶
  • 在匯入階段,解譯器會完整解析 .py 模組的程式碼,並產生執行的 bytecode——這就是可能會發生語法錯誤的地方(如果在本地的 __pycache__ 有最新的 .pyc,這個步驟會被跳過)
  • 雖然編譯(compiling)的確是匯入階段的動作,但這個階段也會發生其他事情——特別是 import 陳述式,它並非只是一個宣告(對比 Java 的 import)——當程序首次匯入模組時(尚無快取時),它會執行被匯入模組的所有最高層級的程式碼,包含執行階段的行為
  • 最高層級的程式碼特指類別的內文(包含嵌套的類別),解譯器會在匯入階段執行它們;相反地,對函式而言,解譯器只會編譯內文、將函式物件加到全域名稱,但不會執行函式內文
    • 類別若是有使用類別修飾器,該修飾器函式也會被執行
    • 類別的中繼類別的方法 __init__ 也會被執行


中繼類別 101

  • 中繼類別是以類別寫成的類別工廠
  • 類別是物件,每一個類別都是某個其他類別的實例——每個類別的中繼類別都是 type(直接或間接)
  • 為了避免無窮回歸(infinite regress),type 是它自己的實例
  • 注意,類別是 type 的實例,不是說類別繼承 type
  • typeobject 有一個獨特的關係
  • 每個中繼類別(例如 ABCMeta)都須要從 type 繼承建構類別的能力
  • 在編寫中繼類別時,將 self 換成 cls 是一種好的作法,可以讓人更清楚知道被建構出來的實例是個類別
  • 你可以在中繼類別編寫 __new__ 來進一步自訂類別,但是往往實作 __init__ 就足夠了
  • 中繼類別的功能:屬性驗證、同時將修飾器套用到多個方法、物件序列化/資料轉換、物件導向映射、物件式持久化、動態轉換其他語言的資料結構

image

image


簡易的中繼類別範例

範例中的類別 Entity 只是為了方便而存在,作為 metaclass=EntityMeta 的範本,後續使用者只需要繼承 Entity 而不需要留意到 EntityMeta 的存在


  • 對某些應用來說,如果可以知道類別屬性的定義順序是很有趣的事情(例如依序對應到 csv 的欄位),但是屬性的 mapping 是無序的,要解決這個問題的方式是使用特殊方法 __prepare__
  • __prepare__ 只跟中繼類別有關,旨在建立類別屬性的 mapping,它會在 __new__ 之前呼叫,可以在此回傳一個 collections.OrderedDict 來保存屬性順序
  • 「不要在產品程式中定義 ABC 或中繼類別……如果你很想做這件事,我敢打賭那可能是『所有問題看起來都是眼中刺』——這是有些人剛拿到嶄新的工具時會犯的毛病——你(與未來維護程式的人)會很開心持續使用簡單、直接的程式,避開這種深奧的東西。」
  • 如果你要編寫中繼程式,或許會希望 Python 提供終極的中繼邊程功能:語法巨集,如同 Elixir 與 Lisp 族群所提供的,Python 相關的函式庫為 MacroPy
  • 學習電腦科學的激進派觀點——「程式已經變得非常巨大複雜,讓人類的心智無法容納。因此,電腦科學教育是教導人們擴充他們的心智來容納程式,學習以更寬廣、強大、更有彈性的概念來思考,每一次思考程式,都會讓程式的功能更強大。」
  • 特性的優點是使用上很簡單,不會影響介面;屬性描述器則相反,提供一個框架來抽象存取邏輯,但因為這個框架很有效率,所以基本的 Python 結構都在私下使用屬性描述器
  • Django、SQLAlchemy 等框架的成功,很大程度上要歸功於中繼類別,許多使用者根本沒有察覺有中繼類別的存在

後記

  • 「如果你是位 Python 使用者,但還沒有加入社群,建議你快點加入。」
  • 「只有真正在做事的人才會犯錯。」

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *