python 在定义元类时,是否有任何理由选择 __new__ 而不是 __init__?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1840421/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 23:11:41  来源:igfitidea点击:

Is there any reason to choose __new__ over __init__ when defining a metaclass?

pythonconstructornew-operatormetaclassinit

提问by Jason Baker

I've always set up metaclasses something like this:

我总是像这样设置元类:

class SomeMetaClass(type):
    def __new__(cls, name, bases, dict):
        #do stuff here

But I just came across a metaclass that was defined like this:

但我刚刚遇到了一个定义如下的元类:

class SomeMetaClass(type):
    def __init__(self, name, bases, dict):
        #do stuff here

Is there any reason to prefer one over the other?

有什么理由更喜欢一个吗?

Update: Bear in mind that I'm asking about using __new__and __init__in a metaclass. I already understand the difference between them in another class. But in a metaclass, I can't use __new__to implement caching because __new__is only called upon class creation in a metaclass.

更新:请记住,我正在询问在元类中使用__new____init__。我已经明白他们在另一堂课上的区别。但是在元类中,我不能__new__用来实现缓存,因为__new__只在元类中调用类创建。

回答by Matt Anderson

If you want to alter the attributes dict before the class is created, or change the bases tuple, you have to use __new__. By the time __init__sees the arguments, the class object already exists. Also, you have to use __new__if you want to return something other than a newly created class of the type in question.

如果要在创建类之前更改属性 dict,或更改基元组,则必须使用__new__. 当__init__看到参数时,类对象已经存在。此外,__new__如果要返回相关类型的新创建类以外的其他内容,则必须使用。

On the other hand, by the time __init__runs, the class does exist. Thus, you can do things like give a reference to the just-created class to one of its member objects.

另一方面,随着时间的推移__init__,该类确实存在。因此,您可以执行一些操作,例如将刚刚创建的类的引用提供给其成员对象之一。

Edit: changed wording to make it more clear that by "object", I mean class-object.

编辑:更改措辞以使其更清楚,“对象”是指类对象。

回答by Ben Blank

You can see the full writeup in the official docs, but basically, __new__is called beforethe new object is created (for the purpose of creating it) and __init__is called afterthe new object is created (for the purpose of initializing it).

您可以在官方文档中看到完整的说明,但基本上,创建新对象之前__new__调用(为了创建它)并创建新对象之后调用(为了初始化它)。__init__

Using __new__allows tricks like object caching (always returning the same object for the same arguments rather than creating new ones) or producing objects of a different class than requested (sometimes used to return more-specific subclasses of the requested class). Generally, unless you're doing something pretty odd, __new__is of limited utility. If you don't need to invoke such trickery, stick with __init__.

使用__new__允许诸如对象缓存(总是为相同的参数返回相同的对象而不是创建新的对象)或生成与请求的类不同的对象(有时用于返回所请求类的更具体的子类)之类的技巧。通常,除非您正在做一些非常奇怪的事情,否则__new__实用性有限。如果您不需要调用此类技巧,请坚持使用__init__.

回答by Debilski

As has been said, if you intend to alter something like the base classes or the attributes, you'll have to do it in __new__. The same is true for the nameof the class but there seems to be a peculiarity with it. When you change name, it is not propagated to __init__, even though, for example attris.

如前所述,如果您打算更改基类或属性之类的内容,则必须在__new__. 类的也是如此,name但它似乎有一个特点。当您更改 时name,它不会传播到__init__,即使例如attr是。

So you'll have:

所以你会有:

class Meta(type):
    def __new__(cls, name, bases, attr):
        name = "A_class_named_" + name
        return type.__new__(cls, name, bases, attr)

    def __init__(cls, name, bases, attr):
        print "I am still called '" + name + "' in init"
        return super(Meta, cls).__init__(name, bases, attr)

class A(object):
    __metaclass__ = Meta

print "Now I'm", A.__name__

prints

印刷

I am still called 'A' in init
Now I'm A_class_named_A

This is important to know, if __init__calls a super metaclass which does some additional magic. In that case, one has to change the name again before calling super.__init__.

知道这一点很重要,如果__init__调用一个超级元类,它会产生一些额外的魔法。在这种情况下,必须在调用 之前再次更改名称super.__init__

回答by JL Peyret

Several differences, in fact.

事实上,有几个不同之处。

For one thing, the first argument in __new__and __init__are not the same, which isn't helped by everyone just using, cls. Someone pointed this out and it's core to understanding the difference:

一方面,__new____init__中的第一个参数不一样,每个人都只使用 , 并没有帮助cls。有人指出了这一点,这是理解差异的核心:

  • __new__gets the metaclass- MyTypein my example (remember the application-level class is not created yet). This is where you can alter bases(which can cause MRO resolution errors if you're not careful).

  • __init__gets the newly-created application-level class, Barand Fooand, by that time, this class's namespace has been populated, see cls_attribin example below.

  • __new__获取元类-MyType在我的示例中(请记住尚未创建应用程序级类)。这是您可以更改的地方bases(如果您不小心,可能会导致 MRO 解析错误)。

  • __init__获取新创建的应用程序级classBar并且Foo到那时,该类的命名空间已填充,请参见cls_attrib下面的示例。

Sample code:

示例代码:

class Mixin:
    pass

class MyType(type):


    def __new__(mcls, name, bases, attrs, **kwargs):
        print("  MyType.__new__.mcls:%s" % (mcls))

        if not Mixin in bases:
            #could cause MRO resolution issues, but if you want to alter the bases
            #do it here
            bases += (Mixin,)

        #The call to super.__new__ can also modify behavior:
        #                                    classes Foo and Bar are instances of MyType
        return super(MyType, mcls).__new__(mcls, name, bases, attrs)

        #now we're back to the standard `type` 
        #doing this will neuter most of the metaclass behavior, __init__ wont
        #be called.                         
        #return super(MyType, mcls).__new__(type, name, bases, attrs)

    def __init__(cls, name, bases, attrs):
        print("  MyType.__init__.cls:%s." % (cls))

        #I can see attributes on Foo and Bar's namespaces
        print("    %s.cls_attrib:%s" % (cls.__name__, getattr(cls, "cls_attrib", None)))
        return super().__init__(name, bases, attrs)


print("\n Foo class creation:")
class Foo(metaclass=MyType):
    pass


print("\n bar class creation:")
class Bar(Foo):
    #MyType.__init__ will see this on Bar's namespace
    cls_attrib = "some class attribute"

output:

输出:

 Foo class creation:
  MyType.__new__.mcls:<class '__main__.test.<locals>.MyType'>
  MyType.__init__.cls:<class '__main__.test.<locals>.Foo'>.
    Foo.cls_attrib:None

 Bar class creation:
  MyType.__new__.mcls:<class '__main__.test.<locals>.MyType'>
  MyType.__init__.cls:<class '__main__.test.<locals>.Bar'>.
    Bar.cls_attrib:some class attribute

回答by Otto Allmendinger

You can implement caching. Person("Hyman")always returns a new object in the second example while you can lookup an existing instance in the first example with __new__(or not return anything if you want).

您可以实现缓存。Person("Hyman")在第二个示例中始终返回一个新对象,而您可以在第一个示例中查找现有实例__new__(如果需要,也可以不返回任何内容)。