如何在类创建完成后调用__init_subclass__方法?
__init_subclass__与元类__new__执行顺序冲突的问题 先拆解下你的代码执行逻辑:元类A的__new__方法在创建子类C时,会先打印C __new__ 1,接着调用super().__new__(这一步会触发__init_subclass__执行),然后打印C __new__ 2并设置DEFAULT_VALUE = 1——这就直接覆盖了__init_subclass__里设置的0。
要在不修改元类A逻辑的前提下解决这个问题,有几种实用的思路:
方法1:用类的__setattr__钩子拦截元类的属性设置
我们可以在基类B中定义类级别的__setattr__方法,当元类尝试修改DEFAULT_VALUE时,自动替换成我们想要的值:
class A(type): def __new__(mcs, name, bases, attrs): print(f"{name} __new__ 1") klass = super().__new__(mcs, name, bases, attrs) print(f"{name} __new__ 2") klass.DEFAULT_VALUE = 1 return klass class B(metaclass=A): @classmethod def __init_subclass__(cls, **kwargs): cls._desired_default = 0 # 预先存储我们想要的默认值 print(f"{cls.__name__} __init_subclass__") @classmethod def __setattr__(cls, name, value): # 拦截元类对DEFAULT_VALUE的赋值,替换成预存的值 if name == 'DEFAULT_VALUE' and hasattr(cls, '_desired_default'): value = cls._desired_default super().__setattr__(name, value) class C(B): pass print(f"DEFAULT VALUE: {C.DEFAULT_VALUE}")
执行这段代码的输出会变成:
B __new__ 1 B __new__ 2 C __new__ 1 C __init_subclass__ C __new__ 2 DEFAULT VALUE: 0
原理很简单:元类A在__new__里设置klass.DEFAULT_VALUE = 1时,会触发类C的__setattr__方法,我们在这个钩子中把值替换成__init_subclass__预存的_desired_default,从而保留我们想要的设置。
方法2:自定义元类子类继承A,利用元类__init__修正属性
元类的__init__方法是在__new__创建完类对象之后执行的,所以我们可以创建一个A的子类元类,在__init__中覆盖元类A设置的属性:
class A(type): def __new__(mcs, name, bases, attrs): print(f"{name} __new__ 1") klass = super().__new__(mcs, name, bases, attrs) print(f"{name} __new__ 2") klass.DEFAULT_VALUE = 1 return klass # 自定义元类子类,完全不修改原A的逻辑 class MetaA(A): def __init__(cls, name, bases, attrs): super().__init__(name, bases, attrs) # 在元类__new__完成后,修正DEFAULT_VALUE if hasattr(cls, '_desired_default'): cls.DEFAULT_VALUE = cls._desired_default class B(metaclass=A): @classmethod def __init_subclass__(cls, **kwargs): cls._desired_default = 0 print(f"{cls.__name__} __init_subclass__") # 让C使用我们的自定义元类 class C(B, metaclass=MetaA): pass print(f"DEFAULT VALUE: {C.DEFAULT_VALUE}")
这个方法逻辑更清晰,元类的__init__本来就是用于类对象创建后的初始化工作,很适合这种需要修正元类__new__设置的场景。
方法3:用描述符动态返回自定义值
如果不想依赖钩子或元类子类,还可以用描述符来动态返回我们想要的DEFAULT_VALUE,直接绕过元类的直接赋值:
class A(type): def __new__(mcs, name, bases, attrs): print(f"{name} __new__ 1") klass = super().__new__(mcs, name, bases, attrs) print(f"{name} __new__ 2") klass.DEFAULT_VALUE = 1 return klass class DefaultValueDescriptor: def __get__(self, instance, owner): # 优先返回自定义值,没有则用元类的默认值 return getattr(owner, '_desired_default', 1) class B(metaclass=A): DEFAULT_VALUE = DefaultValueDescriptor() @classmethod def __init_subclass__(cls, **kwargs): cls._desired_default = 0 print(f"{cls.__name__} __init_subclass__") class C(B): pass print(f"DEFAULT VALUE: {C.DEFAULT_VALUE}")
当访问C.DEFAULT_VALUE时,会调用描述符的__get__方法,返回我们在__init_subclass__中设置的_desired_default,完全绕过元类设置的属性值。
以上三种方法都不需要修改原元类A的逻辑,你可以根据实际场景选择最合适的方案。
内容的提问来源于stack exchange,提问作者deniska369

