简体   繁体   English

元类的__new__和__init__的参数

[英]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. 在重新调整元类中的newinit时,我对方法调用顺序和不同的参数感到有些惊讶。 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM