先看下面代碼執行的效果:
示例代碼1:
class Test(object):
def __init_subclass__(cls, **kwargs):
print("__init_subclass__", cls, kwargs)
class A(Test, name="張三", age=16):
pass
"""
代碼運行直接輸出下面的結果:
__init_subclass__ <class '__main__.A'> {'name': '張三', 'age': 16}
"""
運行結果:
上面代碼中定義了一個Test類,然後讓A這個類繼承它。發現還沒有實例化,而是在創建類的時候就有輸出結果了。
對於一個類,如果這個類被作為父類繼承,那麼會觸發其內部的__init_subclass__方法,這裡的Test被A繼承,那麼Test中的__init_subclass__就會被觸發。而且看到,裡面的cls,就是定義的字類A,也就是繼承它的類,**kwargs,就是額外傳遞的參數。但是發現,第一個參數不是self,而是cls,而且這個cls還不是Test,而是繼承它的類。其實這個方法是隱式的被classmethod裝飾了。
有時候想控制類的生成過程,怎麼辦呢?顯然可以通過元類的的方式,但是如果場景比較簡單,也沒必要使用元類。直接使用__init_subclass__即可
示例代碼2:
class Test(object):
def __init_subclass__(cls, **kwargs):
for k, v in kwargs.items():
type.__setattr__(cls, k, v)
class A(Test, name="張三", age=16):
pass
print(A.name) # 張三
print(A.age) # 16
運行結果:
可以看到,在不使用元類的情況下,通過__init_subclass__實現了類的自定義過程。當然這比較簡單,也可以實現更復雜的邏輯,在某些場景下,可以替代元類。
示例代碼3:
class TestBase(object):
def __init_subclass__(cls, default_name, **kwargs):
super().__init_subclass__(**kwargs)
cls.default_name = default_name
print(cls.default_name)
class Test(TestBase, default_name="張三"):
pass
class NewTest(Test, default_name="李四"):
pass
運行結果:
在上述代碼3中,當有子類繼承了 TestBase類時,那麼 __init_subclass__ 就會調用。內容也很簡單,父類 TestBase為它的所有子類都設置了default_name 屬性。
__init_subclass__ 就像是個鉤子函數,當子類定義之後觸發。
默認實現 object.__init_subclass__ 不執行任何操作。但默認實現不能傳遞任何參數,否則報錯。
值得注意的是,示例中的 __init_subclass__ 第一個參數是 cls 而不是常見的 self 。這是因為這個方法隱式地被 @classmethod 裝飾。
__init_subclass__() 是鉤子函數,它解決了如何讓父類知道被繼承的問題。鉤子中能改變類的行為,而不必求助與元類或類裝飾器。鉤子用起來也更簡單且容易理解。