元类的__new__和__init__的参数

时间:2019-06-09 12:26:39

标签: python python-3.x metaclass

在元类中重写newinit时,我对方法调用顺序和不同的参数感到惊讶。请考虑以下内容:

class AT(type):
    def __new__(mcs, name, bases, dct):
        print(f"Name as received in new: {name}")
        return super().__new__(mcs, name + 'HELLO', bases + (list,), dct)

    def __init__(cls, name, bases, dct):
        print(f"Name as received in init: {name}")
        pass

class A(metaclass=AT):
    pass

A.__name__

输出为:

Name as received in new: A
Name as received in init: A
'AHELLO'

简而言之,我希望init会收到带有参数AHELLO的{​​{1}}。

我以为name调用了__init__:如果未在覆盖的super().__new__中进行调用,那么不会调用我的__new__

有人可以澄清在这种情况下如何调用__init__吗?

作为参考,我的用例是我想通过仅提供一个“基”类(而不是元组)来简化类的创建,在特殊情况下,我在运行时将其添加到__init__

__new__

但是,我发现我还需要将其添加到if not isinstance(bases, tuple): bases = (bases, ) 中。

2 个答案:

答案 0 :(得分:3)

您的__init__方法显然被调用了,其原因是因为您的__new__方法正在返回类的实例。

来自https://docs.python.org/3/reference/datamodel.html#object.new

  

如果__new__()返回cls的实例,则将像__init__()一样调用新实例的__init__(self[, ...])方法,其中self是新实例,其余参数与传递给__new__()

如您所见,传递给__init__的参数是传递给__new__方法的调用者的参数,而不是使用super调用时。这有点含糊,但是如果您仔细阅读,那就意味着什么。

关于其余部分,它按预期工作:

In [10]: A.__bases__
Out[10]: (list,)

In [11]: a = A()

In [12]: a.__class__.__bases__
Out[12]: (list,)

答案 1 :(得分:1)

事实是,协调普通类的__new____init__调用的是其元类上的__call__方法。默认元类型__call__的{​​{1}}方法中的代码在C中,但是在Python中等效于:

type

这在Python中的大多数对象实例化中都会发生,包括在实例化类本身时-元类被隐式调用为类语句的一部分。在这种情况下,从class type: ... def __call__(cls, *args, **kw): instance = cls.__new__(cls, *args, **kw) # __new__ is actually a static method - cls has to be passed explicitly if isinstance(instance, cls): instance.__init__(*args, **kw) return instance 调用的__new____init__元类本身的方法。在这种情况下,type.__call__充当了“ metametaclass” –一个很少需要的概念,但这正是创建您正在探索的行为的原因。

创建类时,type将负责调用该类(不是元类)type.__new__及其描述符的__init_subclass__方法-因此,“ metametaclass” {{1 }}方法无法控制。

因此,如果您希望以编程方式修改传递给元类__set_name__的args,则“正常”方式将是拥有一个“元类”,该元类继承自__call__并与您的元类不同本身,并覆盖其__init__方法:

type

当然,这是比生产代码中任何人都更想要接近“乌龟到底部”的一种方法。

另一种方法是将修改后的名称保留为元类的属性,以便其__call__方法可以从那里获取所需的信息,而忽略从其自己的元类'{{1}中传入的名称}调用。信息通道可以是元类实例上的普通属性。很好-碰巧“元类实例”是自己创建的类-哦,瞧瞧-传递给class MM(type): def __call__(metacls, name, bases, namespace, **kw): name = modify(name) cls = metacls.__new__(metacls, name, bases, namespace, **kw) metacls.__init__(cls, name, bases, namespace, **kw) return cls # or you could delegate to type.__call__, replacing the above with just # return super().__call__(modify(name), bases, namespace, **kw) 的名称已经记录在__init__属性中。

换句话说,您要使用在元类__call__方法中修改的类名和自己的type.__new__方法一起使用,要做的就是忽略传入的__name__参数,并改用__new__

__init__