[英]Dynamically adding methods with metaclass “__init__” vs “__new__”
[英]Is there any reason to choose __new__ over __init__ when defining a metaclass?
我總是像這樣設置元類:
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__
只在元類中創建類時調用。
如果要在創建類之前更改屬性 dict 或更改基元組,則必須使用__new__
。 當__init__
看到參數時,類對象已經存在。 此外,如果您想返回新創建的相關類型類以外的其他內容,則必須使用__new__
。
另一方面,當__init__
運行時,該類確實存在。 因此,您可以執行一些操作,例如將剛剛創建的類的引用提供給其成員對象之一。
編輯:更改措辭以使其更清楚,“對象”是指類對象。
您可以在官方文檔中看到完整的文章,但基本上, __new__
在創建新對象之前調用(為了創建它),而__init__
在創建新對象之后調用(為了初始化它)。
使用__new__
允許諸如對象緩存(總是為相同的參數返回相同的對象而不是創建新的對象)或生成與請求的類不同的對象(有時用於返回請求類的更具體的子類)之類的技巧。 通常,除非您正在做一些非常奇怪的事情,否則__new__
的效用有限。 如果您不需要調用此類技巧,請堅持使用__init__
。
事實上,有幾個不同之處。
一方面, __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
如前所述,如果您打算更改基類或屬性之類的內容,則必須在__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__
之前再次更改名稱。
您可以實現緩存。 Person("Jack")
在第二個示例中總是返回一個新對象,而您可以在第一個示例中使用__new__
查找現有實例(或者如果需要,則不返回任何內容)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.