57

我总是设置这样的元类:

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__只在元类中创建类时调用。

4

5 回答 5

73

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

另一方面,随着时间的推移__init__,这个类确实存在。因此,您可以做一些事情,比如将刚刚创建的类引用到其成员对象之一。

编辑:更改措辞以使“对象”更清楚,我的意思是类对象。

于 2009-12-03T15:08:54.380 回答
8

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

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

于 2009-12-03T15:16:57.087 回答
6

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

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

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

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

示例代码:

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"

输出:

 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
于 2020-02-03T21:24:29.500 回答
2

如前所述,如果您打算更改基类或属性等内容,则必须在__new__. 类的也是如此,name但它似乎有一个特殊性。当您更改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__

印刷

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

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

于 2011-03-11T21:24:27.567 回答
1

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

于 2009-12-03T15:05:44.297 回答