起源
在研究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"""