定义元类时是否有理由选择 __new__ 而不是 __init__ ?

发布于 2024-08-13 12:08:51 字数 471 浏览 5 评论 0原文

我总是设置这样的元类:

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

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

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

有什么理由比另一个更喜欢其中一个?

更新:请记住,我正在询问有关在元类中使用 __new____init__ 的问题。我在另一堂课上已经明白了他们之间的区别。但在元类中,我无法使用 __new__ 来实现缓存,因为仅在元类中创建类时调用 __new__ 。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

木緿 2024-08-20 12:08:51

如果你想在创建类之前改变属性字典,或者改变基元组,你必须使用__new__。当 __init__ 看到参数时,类对象已经存在。另外,如果您想返回除相关类型的新创建的类之外的其他内容,则必须使用 __new__ 。

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

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

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.

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.

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

如日中天 2024-08-20 12:08:51

事实上,有几个区别。

一方面,__new____init__ 中的第一个参数并不相同,每个坚持只使用 cls 的人都没有明确表示这一点。两种情况(尽管变量名称不具有任何特定含义)。有人指出了这一点,这是理解差异的核心:

  • 在我的示例中,

    __new__ 获取 metaclass - MyType (记住应用程序 -级别类尚未创建)。您可以在此处更改base(如果您不小心,可能会导致 MRO 解析错误)。我将调用该变量 mcls,以区别于通常引用应用程序级类的 cls

  • __init__ 获取新创建的应用程序级 classBarFoo,到那时,此类的命名空间已填充,请参阅下面示例中的 cls_attrib 。按照通常的命名约定,我将坚持使用 cls

示例代码:

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:
#

Several differences, in fact.

For one thing, the first argument in __new__ and __init__ are not the same, which is not made clear by everyone insisting on just using cls in both cases (despite the fact that the variable name doesn't hold any particular meaning). Someone pointed this out and it's core to understanding the difference:

  • __new__ gets the metaclass - MyType in 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). I'll call that variable mcls, to differentiate it from the usual cls referring to application level class.

  • __init__ gets the newly-created application-level class, Bar and Foo and, by that time, this class's namespace has been populated, see cls_attrib in example below. I'll stick to cls as per usual naming convention.

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
自此以后,行同陌路 2024-08-20 12:08:51

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

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

You can see the full writeup in the official docs, but basically, __new__ is called before the new object is created (for the purpose of creating it) and __init__ is called after the new object is created (for the purpose of initializing it).

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__.

淡看悲欢离合 2024-08-20 12:08:51

如前所述,如果您打算更改基类或属性等内容,则必须在 __new__ 中进行。类的名称也是如此,但它似乎有一个特殊之处。当您更改 name 时,它不会传播到 __init__,尽管 attr 是这样。

所以你会得到:

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

如果 __init__ 调用一个超级元类来执行一些额外的魔法,那么了解这一点很重要。在这种情况下,在调用 super.__init__ 之前必须再次更改名称。

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 name of 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 attr is.

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__.

╰◇生如夏花灿烂 2024-08-20 12:08:51

您可以实施缓存。 Person("Jack") 在第二个示例中始终返回一个新对象,而您可以在第一个示例中使用 __new__ 查找现有实例(或者如果您想要,则不返回任何内容) )。

You can implement caching. Person("Jack") 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).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文