簡體   English   中英

在定義元類時,是否有任何理由選擇 __new__ 而不是 __init__?

[英]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__獲取新創建的應用程序級BarFoo ,到那時,該類的命名空間已經填充,請參見下面示例中的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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM