起源
在研究graphql-python源碼的時候被__init_subclass_with_meta__
這個類方法吸引,進而發現除元類外改變子類行為的另一種方式:__init_subclass__
類方法 __init_subclass__
從 3.6 引入,作用是可以在不使用元類的情況下改變子類的行為。也就是說它是獨立於元類編程的,也能達到編輯其他類的一種手段。
示例1
# defining a SuperClass
class SuperClass:
# defining __init_subclass__ method
def __init_subclass__(cls, **kwargs):
cls.default_name ="Inherited Class"
# defining a SubClass
class SubClass(SuperClass):
# an attribute of SubClass
default_name ="SubClass"
print(default_name)
subclass = SubClass()
print(subclass.default_name)
輸出
SubClass
Inherited Class
了解代碼
- 在上面的示例中,有 2 個類(即超類和子類),子類繼承自超類。default_name是子類的一個屬性。
- 屬性default_name的值由 SuperClass 使用__init_subclass__方法更改。
- cls是指繼承的子類。提供給新類的關鍵字參數 (**kwargs) 將傳遞給父類的類__init_subclass__。
- 為了與使用__init_subclass__的其他子類兼容,應該取出所需的關鍵字參數,並將其他子類傳遞給基類(Super Class)。
這個__init_subclass__
子類與Decorator類非常相似。但是,如果類裝飾符只影響它們應用於的特定類,但是__init_subclass__
只應用於定義該方法的類的未來子類。這意味着我們可以改變/定義從超類繼承的任何新類的行為。
示例2
# defining a SuperClass
class SuperClass:
def __init_subclass__(cls, default_name, **kwargs):
cls.default_name = default_name
# defining a subclass
class SubClass1(SuperClass, default_name ="SubClass1"):
pass
# defining another subclass
class SubClass2(SuperClass, default_name ="SubClass2"):
default_name = "InheritedClass"
# references for subclasses
subClass1 = SubClass1()
subClass2 = SubClass2()
print(subClass1.default_name)
print(subClass2.default_name)
輸出
SubClass1
SubClass2
創建類對象后的自定義步驟
盡管 __init_subclass__
是獨立於元類編程的,但類都是由默認元類 type 創建的,那么在 type.__new__()
創建了類之后會有哪些步驟:
- 首先,
type.__new__
收集類命名空間定義的set_name()
方法的所有描述符; - 其次,這些
__set_name__
的特定描述符在特定的情況下調用; - 最后,在父類上調用鈎子
__init_subclass__()
。
若類被裝飾器裝飾,那么就將上述生成的對象傳遞給類裝飾器。
總結
總的來說,__init_subclass__()
是鈎子函數,它解決了如何讓父類知道被繼承的問題。鈎子中能改變類的行為,而不必求助與元類或類裝飾器。鈎子用起來也更簡單且容易理解。
雖然本文還提到了 __set_name__
,但它和 __init_subclass__
並不相互關聯, __set_name__
主要是解決了如何讓描述符知道其屬性的名稱。
__init_subclass__
的目標是提供更簡單的定制方式,在簡單的場景下是元類的替代品。值得試一試。
下面是graphql-python對該方法的使用,值得學習
點擊查看代碼
from inspect import isclass
from .props import props
class SubclassWithMeta_Meta(type):
_meta = None
def __str__(cls):
if cls._meta:
return cls._meta.name
return cls.__name__
def __repr__(cls):
return f"<{cls.__name__} meta={repr(cls._meta)}>"
class SubclassWithMeta(metaclass=SubclassWithMeta_Meta):
"""This class improves __init_subclass__ to receive automatically the options from meta"""
def __init_subclass__(cls, **meta_options):
"""This method just terminates the super() chain"""
_Meta = getattr(cls, "Meta", None)
_meta_props = {}
if _Meta:
if isinstance(_Meta, dict):
_meta_props = _Meta
elif isclass(_Meta):
_meta_props = props(_Meta)
else:
raise Exception(
f"Meta have to be either a class or a dict. Received {_Meta}"
)
delattr(cls, "Meta")
options = dict(meta_options, **_meta_props)
abstract = options.pop("abstract", False)
if abstract:
assert not options, (
"Abstract types can only contain the abstract attribute. "
f"Received: abstract, {', '.join(options)}"
)
else:
super_class = super(cls, cls)
if hasattr(super_class, "__init_subclass_with_meta__"):
super_class.__init_subclass_with_meta__(**options)
@classmethod
def __init_subclass_with_meta__(cls, **meta_options):
"""This method just terminates the super() chain"""