You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在类创建完成后调用__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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 09:47:38