Python 3元类机制解惑:方法调用顺序、参数与返回值
嘿,我来帮你把这些元类的细节掰扯清楚——先从你代码里的核心问题说起,再逐个拆解每个方法的调用时机、参数、返回值和相互逻辑~
先解决你的代码问题:为什么Thing的__init__没被调用?
你的元类Logged的__call__方法有两个明显的问题:
- 参数定义错误:元类的
__call__第一个参数应该是元类的实例(也就是你定义的Thing类本身),后面还得接收传给类构造的任意参数(*args, **kwargs),而不是只写个subclass。 - 缺少实例初始化步骤:当你调用
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__处理后的实例)。你也可以自定义返回逻辑,比如单例模式下始终返回同一个实例。 - 内部流程:
- 调用
cls.__new__(cls, *args, **kwargs)生成实例对象 - 如果
__new__返回的是cls的实例,调用cls.__init__(instance, *args, **kwargs)初始化实例 - 返回最终的实例给调用者
- 调用
完整的调用顺序总结
当你定义并使用一个带元类的类时,整个流程的顺序是:
- Python解析类定义,调用元类的
__prepare__获取属性映射 - 收集类的所有属性到这个映射中
- 调用元类的
__new__创建类对象 - 调用元类的
__init__初始化类对象 - 当你调用类创建实例时(
Thing()),触发元类的__call__ - 在
__call__内部,调用类的__new__创建实例 - 如果实例有效,调用类的
__init__初始化实例 - 返回实例给调用者
这样梳理下来,你应该就能清晰理解每个元类方法的角色和相互关系啦!
内容的提问来源于stack exchange,提问作者Anthony
相关产品推荐
相关产品推荐

