在研究graphql-python源码的时候被__init_subclass_with_meta__
这个类方法吸引,进而发现除元类外改变子类行为的另一种方式:__init_subclass__
类方法 __init_subclass__
从 3.6 引入,作用是可以在不使用元类的情况下改变子类的行为。也就是说它是独立于元类编程的,也能达到编辑其他类的一种手段。
# 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
了解代码
这个__init_subclass__
子类与Decorator类非常相似。但是,如果类装饰符只影响它们应用于的特定类,但是__init_subclass__
只应用于定义该方法的类的未来子类。这意味着我们可以改变/定义从超类继承的任何新类的行为。
# 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"""