簡體   English   中英

使用superclass .__ init()__而不是superclass()調用超類構造函數的原因

[英]Reason for calling super class constructor using superclass.__init()__ instead of superclass()

我是Python的初學者,並且使用Lutz的書來理解Python中的OOPS。 這個問題可能是基本的,但我將不勝感激。 我研究了SO,找到了“如何”的答案,但沒有找到“為什么”的答案。

當我從書上知道,如果Sub繼承了Super然后一個不需要調用父類的( Super的) __init__()方法。

例:

class Super:
    def __init__(self,name):
        self.name=name
        print("Name is:",name)

class Sub(Super):
    pass

a = Sub("Harry")
a.name

上面的代碼確實將屬性name分配給對象a 它還會按預期打印name

但是,如果我將代碼修改為:

class Super:
    def __init__(self,name):
        print("Inside Super __init__")
        self.name=name
        print("Name is:",name)

class Sub(Super):
      def __init__(self,name):
          Super(name) #Call __init__ directly

a = Sub("Harry")
a.name

上面的代碼無法正常工作。 通過罰款,我的意思是,雖然Super.__init__()不會被調用(從打印語句看到的),沒有連接到屬性a 當我運行a.name ,出現錯誤, AttributeError: 'Sub' object has no attribute 'name'

我在SO上對此主題進行了研究,並找到了有關在python中調用鏈調用父構造函數的修復程序,以及為什么超類__init__方法不會自動調用嗎?

這兩個線程討論如何修復它,但是沒有提供原因。

問題: 為什么我需要使用Super.__init__(self, name)super(Sub, self).__init__(name)而不是直接調用Super(name)來調用Super__init__

Super.__init__(self, name)Super(name) ,我們看到調用了Super的__init__() (從打印語句中看到),但是僅在Super.__init__(self, name)我們看到了屬性附屬於Sub類。

Super(name)不會自動將self (子)對象傳遞給Super嗎? 現在,您可能會問我如何知道self被自動傳遞? 如果將Super(name)修改為Super(self,name) ,則會收到一條錯誤消息,指出TypeError: __init__() takes 2 positional arguments but 3 were given 據我了解, self是自動傳遞的。 因此,有效地,我們最終兩次通過self

我不知道為什么即使Super.__init__()運行, Super(name)也不將name屬性附加到Sub 我將不勝感激。


作為參考,以下是根據我對SO的研究得出的代碼的工作版本:

class Super:
    def __init__(self,name):
        print("Inside __init__")
        self.name=name
        print("Name is:",name)

class Sub(Super):
    def __init__(self,name):
        #Super.__init__(self, name) #One way to fix this
        super(Sub, self).__init__(name) #Another way to fix  this

a = Sub("Harry")
a.name

PS:我在Anaconda發行版下使用Python-3.6.5

當我從書上知道,如果子繼承了超級然后一個不需要調用父類的( Super的) __init__()方法。

這是誤導。 的確,您不需要調用超類的__init__方法,但是,如果不這樣做,則__init__任何操作都不會發生。 對於普通班級,所有這些都需要完成。 有時很有用,通常是在未將類設計為繼承自此類的情況下,如下所示:

class Rot13Reader:
    def __init__(self, filename):
        self.file = open(filename):
    def close(self):
        self.file.close()
    def dostuff(self):
        line = next(file)
        return codecs.encode(line, 'rot13')

想象一下,您需要此類的所有行為,但要使用字符串而不是文件。 唯一的方法是跳過open

class LocalRot13Reader(Rot13Reader):
    def __init__(self, s):
        # don't call super().__init__, because we don't have a filename to open
        # instead, set up self.file with something else
        self.file = io.StringIO(s)

在這里,我們要避免在超類中分配self.file 在你的情況,因為幾乎所有的類你曾經打算寫你想要避免self.name在超分配。 這就是為什么即使Python 允許您不調用超類的__init__ ,您也幾乎總是調用它的原因。

注意,這里的__init__沒有什么特別的。 例如,我們可以重寫dostuff以調用基類的版本,然后執行其他操作:

def dostuff(self):
    result = super().dostuff()
    return result.upper()

…或我們可以重寫close並有意不調用基類:

def close(self):
    # do nothing, including no super, because we borrowed our file

唯一的區別是,避免調用基類的充分理由在普通方法中比在__init__更常見。


問題:為什么我需要使用Super.__init__(self, name)super(Sub, self).__init__(name)而不是直接調用Super(name)來調用Super's __init__

因為這些功能有很大不同。

Super(name)構造一個新的Super實例,在其上調用__init__(name) ,並將其返回給您。 然后,您將忽略該值。

特別是, Super.__init__確實會被調用一次,但是調用它的self是新的Super實例,在Super(name)情況下,您只是將其丟棄,而它是您自己的selfsuper(Sub, self).__init__(name)情況下。

因此,在第一種情況下,它將在其他一些被丟棄的對象上設置name屬性,而沒有人在您的對象上設置它,這就是為什么self.name稍后引發AttributeError

如果您在兩個類的__init__方法中都添加了一些內容以顯示所涉及的實例,則可能有助於您理解這一點:

class Super:
    def __init__(self,name):
        print(f"Inside Super __init__ for {self}")
        self.name=name
        print("Name is:",name)

class Sub(Super):
    def __init__(self,name):
        print(f"Inside Sub __init__ for {self}")
        # line you want to experiment with goes here.

如果最后一行是super().__init__(name)super(Sub, self).__init__name)Super.__init__(self, name) ,您將看到以下內容:

Inside Sub __init__ for <__main__.Sub object at 0x10f7a9e80>
Inside Super __init__ for <__main__.Sub object at 0x10f7a9e80>

請注意,在兩種情況下,它都是同一個對象,即地址為0x10f7a9e80的Sub

…但是如果最后一行是Super(name)

Inside Sub __init__ for <__main__.Sub object at 0x10f7a9ea0>
Inside Super __init__ for <__main__.Super object at 0x10f7a9ec0>

現在,我們有兩個不同的對象,它們的地址分別為0x10f7a9ea0和0x10f7a9ec0,並且類型不同。


如果您對所有魔術的外觀都感到好奇,那么Super(name)執行以下操作(簡化一些,並跳過一些步驟1 ):

_newobj = Super.__new__(Super)
if isinstance(_newobj, Super):
    Super.__init__(_newobj, name)

…而super(Sub, self).__init__(name)則是這樣的:

_basecls = magically_find_next_class_in_mro(Sub)
_basecls.__init__(self, name)

附帶說明一下,如果一本書告訴您使用super(Sub, self).__init__(name)Super.__init__(self, name) ,則可能是為Python 2寫的過時的書。

在Python 3中,您只需執行以下操作:

  • super().__init__(name) :按方法解析順序調用正確的下一個超類。 您幾乎總是想要這個。
  • super(Sub, self).__init__(name) :調用正確的下一個超類-除非您犯了一個錯誤並在那里使Sub錯誤。 僅當您要編寫必須在2.7和3.x中運行的雙版本代碼時,才需要此代碼。
  • Super.__init__(self, name) :調用Super ,無論它是否是正確的下一個超類。 僅當方法解析順序錯誤並且需要解決時,才需要此方法。 2

如果您想了解更多,這些都在文檔中,但這可能有點令人生畏:

super__new__以及所有相關功能原始介紹對我理解所有這些非常有幫助。 我不確定這是否會對那些已經不了解老式Python類的人有幫助,但是它寫得不錯,Guido(顯然)知道他在說什么,所以值得一讀。


1.這種解釋中最大的_baseclass是, super實際上返回了一個代理對象,該代理對象的行為類似於_baseclass綁定到self ,其方式與方法綁定的方式相同,可用於綁定諸如__init__方法。 如果您知道方法的工作原理,那么這是有用的/有趣的知識,但如果您不知道方法的話,可能只是額外的困惑。

2.…或如果您正在使用不支持super (或正確的方法解析順序)的舊式類。 沒有老式類的Python 3永遠不會出現這種情況。 但是,不幸的是,您會在許多tkinter示例中看到它,因為最好的教程仍然是Effbot,它是為Python 2.3編寫的,當時Tkinter都是老式類,並且從未進行過更新。

Super(name)不是對超類__init__的“直接調用”。 畢竟,您叫Super ,而不是Super.__init__

Super.__init__接受未初始化的Super實例並對其進行初始化。 Super 創建並初始化了一個與要初始化的實例完全獨立的新實例(然后立即將新實例扔掉了)。 您要初始化的實例未更改。

Super(name)實例化super的新實例。 想想這個例子:

def __init__(self, name):
    x1 = Super(name)
    x2 = Super("some other name")
    assert x1 is not self
    assert x2 is not self

為了在當前實例上顯式調用Super的構造函數,您必須使用以下語法:

def __init__(self, name):
    Super.__init__(self, name)

現在,如果您是初學者,也許您不想進一步閱讀。

如果這樣做,您會發現有充分的理由使用super(Sub, self).__init__(name) (或Python 3中的super().__init__(name) )而不是Super.__init__(self, name)


只要您確定Super實際上是您的超類Super.__init__(self, name)可以正常工作。 但實際上,您永遠都不知道。

您可能具有以下代碼:

class Super:
    def __init__(self):
        print('Super __init__')

class Sub(Super):
    def __init__(self):
        print('Sub __init__')
        Super.__init__(self)

class Sub2(Super):
    def __init__(self):
        print('Sub2 __init__')
        Super.__init__(self)

class SubSub(Sub, Sub2):
    pass

您現在可以期望SubSub()最終調用上述所有構造函數,但不會:

>>> x = SubSub()
Sub __init__
Super __init__
>>>

要更正它,您必須執行以下操作:

class Super:
    def __init__(self):
        print('Super __init__')

class Sub(Super):
    def __init__(self):
        print('Sub __init__')
        super().__init__()

class Sub2(Super):
    def __init__(self):
        print('Sub2 __init__')
        super().__init__()

class SubSub(Sub, Sub2):
    pass

現在可以正常工作:

>>> x = SubSub()
Sub __init__
Sub2 __init__
Super __init__
>>>

原因是Sub SubSub類被聲明為Super ,如果SubSub類中有多個繼承,Python的MRO將繼承建立為: SubSub繼承自SubSub繼承自Sub2Sub2繼承自SuperSuper繼承自object

您可以測試:

>>> SubSub.__mro__
(<class '__main__.SubSub'>, <class '__main__.Sub'>, <class '__main__.Sub2'>, <class '__main__.Super'>, <class 'object'>)

現在,每個類的構造函數中的super()調用都會在MRO中找到下一個類,以便可以調用該類的構造函數。

參見https://www.python.org/download/releases/2.3/mro/

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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