元类中__new__与__init__的参数及调用顺序疑问
一、为什么__init__的name参数还是原始值?
先直接点破核心:Python在调用元类的__init__时,传入的是类定义时的原始参数,而不是__new__方法修改后的值。
我们拆解一下你的代码执行流程:
- 当你定义
class A(metaclass=AT): pass时,Python会先收集三个核心参数:name='A'(类名)bases=()(基类元组)dct={}(类命名空间的字典)
- 调用元类的
__new__方法:AT.__new__(AT, 'A', (), {})。你在这个方法里修改了name(追加HELLO)和bases(添加list作为基类),然后调用super().__new__创建并返回了一个新的类对象——这个对象的__name__被设为了'AHELLO'。 - 接下来Python会自动调用元类的
__init__方法,但注意:它传入的参数是原始的name='A'、bases=()、dct={},再加上刚才__new__返回的类对象(也就是cls参数)。
这就是为什么你看到__init__里打印的name是A,但A.__name__是AHELLO——__init__接收的是定义时的原始参数,而__new__修改的是返回的类对象本身的属性。
另外纠正你的一个误解:__init__不是由super().__new__调用的,而是Python解释器在__new__返回类对象后自动触发的。只要__new__返回的是当前元类(或其子类)的实例,Python就会调用__init__;只有当__new__返回一个已经存在的类对象时,__init__才会被跳过。
二、为什么处理bases时__new__和__init__都要加转换代码?
同样的逻辑:__init__接收的bases是类定义时的原始值,不是__new__里修改后的版本。
比如你如果动态创建类时传入了单个基类(而非元组),__new__里收到的bases是那个单个类,你转成元组后传给super().__new__,用来创建类的基类会是正确的元组格式,但__init__收到的bases还是原始的单个类。如果你的__init__方法里有需要处理bases的逻辑(比如遍历基类、检查基类属性等),就必须再次转换,因为__init__的bases参数是未处理的原始值。
优化方案:避免重复处理
如果你不想在__new__和__init__里重复写转换代码,可以在__new__里把处理后的bases存储到类对象的一个自定义属性里,然后__init__直接用这个属性:
class AT(type): def __new__(mcs, name, bases, dct): # 统一处理bases格式 if not isinstance(bases, tuple): bases = (bases,) # 创建类对象并存储处理后的bases new_cls = super().__new__(mcs, name, bases, dct) new_cls._processed_bases = bases return new_cls def __init__(cls, name, bases, dct): # 直接使用预处理后的bases,无需重复转换 print(f"Processed bases: {cls._processed_bases}") pass
总结一下元类的核心调用逻辑
__new__:创建类对象的工厂方法,返回的是最终的类实例,可以修改类的属性(如__name__、__bases__)或调整创建参数。__init__:初始化已创建的类对象,接收的是类定义时的原始参数,而非__new__修改后的参数。- 执行顺序:收集原始参数 → 调用
__new__创建类 → 若返回的是当前元类实例,调用__init__初始化。
内容的提问来源于stack exchange,提问作者Cedric H.

