理解 Python 中的 __init_subclass__

在Python的世界裏,幾乎全部的東西都是可變的。html

起步

類方法 __init_subclass__3.6whatsnew3.6) 引入,做用是能夠在不使用元類的狀況下改變子類的行爲。也就是說它是獨立於元類編程的,也能達到編輯其餘類的一種手段。那麼,如何來理解它呢?python

從示例入手

按照文檔中的示例:編程

class Philosopher:
    def __init_subclass__(cls, default_name, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.default_name = default_name

class AustralianPhilosopher(Philosopher, default_name="Bruce"):
    pass
複製代碼

示例中,當有子類繼承了 Philosopher ,那麼 __init_subclass__ 就會調用。內容也很簡單,父類 Philosopher 爲它的全部子類都設置了 default_name 屬性。bash

__init_subclass__ 就像是個鉤子函數,當子類定義以後觸發。函數

默認實現 object.__init_subclass__ 不執行任何操做。但默認實現不能傳遞任何參數,不然報錯。ui

值得注意的是,示例中的 __init_subclass__ 第一個參數是 cls 而不是常見的 self 。這是由於這個方法隱式地被 @classmethod 裝飾(PEP-487)。這個決定實際上是因爲多數人忘記給 __prepare__ 加上 @classmethod 裝飾了,帶來了很差的用戶體驗;並且在另外一個PEP中 __prepare__ 被記錄爲普通的方法,因此已經很差改它了。而做爲 3.6 新方法的 __init_subclass__ 所以就有理由隱式裝飾了(其實我比較喜歡顯式,由於 Python 之禪中提到的 "顯示優於隱式")。spa

建立類對象後的自定義步驟

儘管 __init_subclass__ 是獨立於元類編程的,但類都是由默認元類 type 建立的,那麼在 type.__new__() 建立了類以後會有哪些步驟:code

  1. 首先,type.__new__ 收集類命名空間定義的 set_name() 方法的全部描述符;
  2. 其次,這些 __set_name__ 的特定描述符在特定的狀況下調用;
  3. 最後,在父類上調用鉤子 __init_subclass__()

若類被裝飾器裝飾,那麼就將上述生成的對象傳遞給類裝飾器。htm

總結

總的來講,__init_subclass__() 是鉤子函數,它解決了如何讓父類知道被繼承的問題。鉤子中能改變類的行爲,而沒必要求助與元類或類裝飾器。鉤子用起來也更簡單且容易理解。對象

雖然本文還提到了 __set_name__ ,但它和 __init_subclass__ 並不相互關聯, __set_name__ 主要是解決了如何讓描述符知道其屬性的名稱。

__init_subclass__ 的目標是提供更簡單的定製方式,在簡單的場景下是元類的替代品。值得試一試。

相關文章
相關標籤/搜索