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

Python 3元类机制解惑:方法调用顺序、参数与返回值

嘿,我来帮你把这些元类的细节掰扯清楚——先从你代码里的核心问题说起,再逐个拆解每个方法的调用时机、参数、返回值和相互逻辑~

先解决你的代码问题:为什么Thing的__init__没被调用?

你的元类Logged的__call__方法有两个明显的问题:

  1. 参数定义错误:元类的__call__第一个参数应该是元类的实例(也就是你定义的Thing类本身),后面还得接收传给类构造的任意参数(*args, **kwargs),而不是只写个subclass。
  2. 缺少实例初始化步骤:当你调用Thing()创建实例时,元类的__call__负责协调完整的实例创建流程——不仅要调用__new__生成实例对象,还要调用__init__去初始化这个实例(只要实例是当前类的对象)。

修正后的__call__方法应该是这样:

def __call__(cls, *args, **kwargs):
    print('In meta __call__')
    print('Creating {}.'.format(cls))
    # 1. 调用类的__new__创建实例
    instance = cls.__new__(cls, *args, **kwargs)
    # 2. 如果实例是当前类的对象,调用__init__初始化
    if isinstance(instance, cls):
        cls.__init__(instance, *args, **kwargs)
    # 3. 返回最终的实例
    return instance

运行这段修正后的代码,你就能看到In sub __init__的输出了,完整输出会变成:

In meta __prepare__
In meta __new__
In meta __init__
In meta __call__
Creating <class '__main__.Thing'>.
In sub __new__
In sub __init__
hello

元类核心方法的细节拆解

接下来我们逐个梳理__prepare__、__new__、__init__、__call__的核心细节:

1. __prepare__:类属性的“收纳盒”创建者

  • 调用时机:Python开始解析类定义里的属性之前,会先调用这个方法。
  • 参数:
    • cls:元类本身(比如你的Logged)
    • name:要创建的类的名字(比如Thing)
    • bases:要创建的类的父类元组(比如(),因为Thing没有显式父类)
  • 返回值:必须返回一个映射对象(比如普通字典,或者collections.OrderedDict),用来临时存储类定义里的所有属性(方法、变量等)。你可以自定义这个映射来实现特殊逻辑,比如控制属性的顺序。
  • 位置:是类创建流程的第一步,早于__new__和__init__。

2. __new__:类对象本身的“构造器”

  • 调用时机:__prepare__返回映射后,Python收集完类的所有属性,就会调用这个方法来创建类对象本身(也就是Thing这个类,它是元类Logged的实例)。
  • 参数:
    • cls:元类本身(Logged)
    • name:类名(Thing)
    • superclasses:父类元组(())
    • attributes:类的属性字典(包含__prepare__返回的映射里的所有内容,加上Python自动添加的属性)
    • 可选**kwargs:如果定义类时传入了除metaclass之外的关键字参数,会被这里接收
  • 返回值:必须返回一个类对象(通常是调用type.__new__(cls, name, superclasses, attributes)来创建,你也可以自定义这个过程,比如动态修改属性后再创建类)。如果返回的不是类对象,后续的__init__不会被执行。
  • 位置:在__prepare__之后,__init__之前。

3. __init__:类对象的“初始化器”

  • 调用时机:元类的__new__成功创建类对象后,会调用这个方法来初始化这个类对象。
  • 参数:
    • cls:刚创建的类对象(也就是Thing——注意这里和__new__的cls不一样!__new__的cls是元类,__init__的cls是元类的实例,也就是我们定义的类)
    • name:类名(Thing)
    • superclasses:父类元组(())
    • attributes:类的属性字典
    • 可选**kwargs:和__new__的kwargs对应
  • 返回值:必须是None(Python规定初始化方法不能返回任何值)。
  • 作用:你可以在这里给类对象添加额外属性、修改现有属性,或者执行一些类级别的初始化逻辑。

4. __call__:实例创建的“总指挥”

  • 调用时机:当你调用类对象创建实例时(比如Thing()),实际上是调用元类的__call__方法——因为类对象是元类的实例,调用实例就会触发它所属类的__call__方法。
  • 参数:
    • cls:类对象(Thing)
    • *args, **kwargs:传给类构造的所有参数(比如Thing(1, name="test")中的1和name="test")
  • 返回值:返回创建好的实例对象(通常是经过__new__和__init__处理后的实例)。你也可以自定义返回逻辑,比如单例模式下始终返回同一个实例。
  • 内部流程:
    1. 调用cls.__new__(cls, *args, **kwargs)生成实例对象
    2. 如果__new__返回的是cls的实例,调用cls.__init__(instance, *args, **kwargs)初始化实例
    3. 返回最终的实例给调用者

完整的调用顺序总结

当你定义并使用一个带元类的类时,整个流程的顺序是:

  1. Python解析类定义,调用元类的__prepare__获取属性映射
  2. 收集类的所有属性到这个映射中
  3. 调用元类的__new__创建类对象
  4. 调用元类的__init__初始化类对象
  5. 当你调用类创建实例时(Thing()),触发元类的__call__
  6. 在__call__内部,调用类的__new__创建实例
  7. 如果实例有效,调用类的__init__初始化实例
  8. 返回实例给调用者

这样梳理下来,你应该就能清晰理解每个元类方法的角色和相互关系啦!

内容的提问来源于stack exchange,提问作者Anthony

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:54:53