[英]Arguments of __new__ and __init__ for metaclasses
I am a bit surprised by the method call order and the different arguments when overriding new
and init
in a metaclass. 在重新调整元类中的new
和init
时,我对方法调用顺序和不同的参数感到有些惊讶。 Consider the following: 考虑以下:
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__
The output is: 输出是:
Name as received in new: A
Name as received in init: A
'AHELLO'
In short I would have expected init
to receive AHELLO
with the argument name
. 简而言之,我希望init
能够使用参数name
接收AHELLO
。
I imagined that __init__
was called by super().__new__
: if the call is not done in the overridden __new__
then my __init__
is not called. 我想是__init__
是由super().__new__
调用的:如果调用没有在被覆盖的__new__
完成,那么我的__init__
就不会被调用。
Could someone clarify how __init__
is called in this case? 有人可以澄清在这种情况下如何调用__init__
?
For information my use case for this is that I wanted to make creation of classes, in a special case, easier at runtime by providing only a single "base" class (and not a tuple), I then added this code in __new__
: 有关信息,我的用例就是我想在特殊情况下创建类,在运行时通过只提供一个“基类”(而不是元组)更容易,然后我在__new__
添加了这些代码:
if not isinstance(bases, tuple):
bases = (bases, )
however, I found out that I also need to add it in __init__
. 但是,我发现我还需要在__init__
添加它。
Your __init__
method is obviously called and the reason for that is because your __new__
method is returning an instance of your class. 你的__init__
方法显然被调用了,原因是因为你的__new__
方法正在返回你的类的一个实例。
From https://docs.python.org/3/reference/datamodel.html#object. 来自https://docs.python.org/3/reference/datamodel.html#object。 new : 新的 :
If
__new__()
returns an instance of cls, then the new instance's__init__()
method will be invoked like__init__(self[, ...])
, where self is the new instance and the remaining arguments are the same as were passed to__new__()
. 如果__new__()
返回一个cls实例,那么新实例的__init__()
方法将被调用,如__init__(self[, ...])
,其中self是新实例,其余参数与传递给的相同。__new__()
。
As you can see the arguments passed to __init__
are those passed to __new__
method's caller not when you call it using super
. 正如您所看到的,传递给__init__
的参数是那些传递给__new__
方法的调用者的参数,而不是当您使用super
调用它时。 It's a little bit vague but that's what it means if you read it closely. 这有点模糊,但如果你仔细阅读它就意味着什么。
And regarding the rest it works just as expected: 关于其余部分,它的工作方式与预期一致:
In [10]: A.__bases__
Out[10]: (list,)
In [11]: a = A()
In [12]: a.__class__.__bases__
Out[12]: (list,)
The fact is that what orchestrates the call of __new__
and __init__
of an ordinary class is the __call__
method on its metaclass. 事实是,编排普通类的__new__
和__init__
的__call__
是其元类的__call__
方法。 The code in the __call__
method of type
, the default metatype, is in C, but the equivalent of it in Python would be: type
的__call__
方法中的代码(默认元type
)在C中,但在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
That takes place for most object instantiation in Python, including when instantiating classes themselves - the metaclass is implicitly called as part of a class statement. 这发生在Python中的大多数对象实例化中,包括在实例化类本身时 - 元类被隐式地称为类语句的一部分。 In this case, the __new__
and __init__
called from type.__call__
are the methods on the metaclass itself. 在这种情况下,从type.__call__
调用的__new__
和__init__
是元类本身的方法。 And in this case, type
is acting as the "metametaclass" - a concept seldom needed, but it is what creates the behavior you are exploring. 在这种情况下, type
充当“metametaclass” - 一个很少需要的概念,但它是创造您正在探索的行为的原因。
When creating classes, type.__new__
will be responsible for calling the class (not the metaclass) __init_subclass__
, and its descriptors' __set_name__
methods - so, the "metametaclass" __call__
method can't control that. 当创建类, type.__new__
将负责调用类(未元类) __init_subclass__
,其描述符__set_name__
方法-是这样,‘metametaclass’ __call__
方法不能控制。
So, if you want the args passed to the metaclass __init__
to be programmatically modified, the "normal" way will be to have a "metametaclass", inheriting from type
and distinct from your metaclass itself, and override its __call__
method: 因此,如果您希望以编程方式修改传递给元类__init__
的args,那么“正常”方式将是一个“metametaclass”,继承自type
并与您的元类本身不同,并覆盖其__call__
方法:
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)
Of course that is a way to get to go closer to "turtles all way to the bottom" than anyone would ever like in production code. 当然,这是一种比生产代码中任何人都希望的更接近“乌龟一直到底”的方法。
An alternative is to keep the modified name as an attribute on the metaclass, so that its __init__
method can take the needed information from there, and ignore the name passed in from its own metaclass' __call__
invocation. 另一种方法是将修改后的名称保留为元类的属性,以便其__init__
方法可以从那里获取所需的信息,并忽略从其自己的元类' __call__
调用传入的名称。 The information channel can be an ordinary attribute on the metaclass instance. 信息通道可以是元类实例上的普通属性。 Well - it happens that the "metaclass instance" is the class being created itself - and oh, see - that the name passed to type.__new__
already gets recorded in it - on the __name__
atribute. 好吧 - 碰巧“元类实例”是自己创建的类 - 哦,看到 - 传递给type.__new__
的名称已经记录在它中 - 在__name__
属性上。
In other words, all you have to do to use a class name modified in a metaclass __new__
method in its own __init__
method, is to ignore the passed in name
argument, and use cls.__name__
instead: 换句话说,在自己的__init__
方法中使用在元类__new__
方法中修改的类名所__new__
就是忽略传入的name
参数,并使用cls.__name__
代替:
class Meta(type):
def __new__(mcls, name, bases, namespace, **kw):
name = modified(name)
return super().__new__(mcls, name, bases, namespace, **kw)
def __init__(cls, name, bases, namespace, **kw):
name = cls.__name__ # noQA (otherwise linting tools would warn on the overriden parameter name)
...
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.