我总是设置类似这样的元类:
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__
仅在元类中创建类时被调用。
答案 0 :(得分:47)
如果要在创建类之前更改属性dict,或更改基本元组,则必须使用__new__
。当__init__
看到参数时,类对象已经存在。此外,如果要返回除了新创建的类型之外的其他内容,则必须使用__new__
。
另一方面,在__init__
运行时,该类确实存在。因此,您可以执行诸如将刚刚创建的类的引用提供给其成员对象之一。
编辑:更改了措辞,以便更清楚地说“对象”,我的意思是类对象。
答案 1 :(得分:4)
您可以在the official docs中看到完整的文章,但基本上,{<1}}在创建新对象之前被称为(为了创建它)和{{在创建新对象之后调用(为了初始化它)。
使用__new__
允许像对象缓存这样的技巧(总是为相同的参数返回相同的对象而不是创建新的对象)或者生成与请求不同的类的对象(有时用于返回更具体的子类)要求的课程)。一般来说,除非你做的事情很奇怪,否则__init__
的效用有限。如果您不需要调用此类技巧,请坚持使用__new__
。
答案 2 :(得分:3)
实际上有几个差异。
一方面,__new__
和__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
答案 3 :(得分:1)
您可以实施缓存。 Person("Jack")
总是在第二个示例中返回一个新对象,而您可以使用__new__
在第一个示例中查找现有实例(或者如果需要,不返回任何内容)。
答案 4 :(得分:1)
如前所述,如果您打算更改基类或属性之类的内容,则必须在__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__
之前再次更改名称。