class super用法 - 用__init __()方法理解Python super()




python繼承init __用法 (7)

使用Python 2.7,我相信自從版本2.2中引入了super()如果其中一個父級繼承自最終繼承object的類( 新式super()則只能調用super() )。

就個人而言,至於python 2.7代碼,我將繼續使用BaseClassName.__init__(self, args)直到我真正獲得使用super()的優勢。

這個問題在這裡已有答案:

我試圖了解super()的用法。 從它的外觀來看,可以創建兩個子類,就好了。

我很想知道以下兩個子課程之間的實際差異。

class Base(object):
    def __init__(self):
        print "Base created"

class ChildA(Base):
    def __init__(self):
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        super(ChildB, self).__init__()

ChildA() 
ChildB()

真的沒有。 super()查看MRO中的下一個類(方法解析順序,使用cls.__mro__訪問)來調用方法。 只是調用基__init__調用基__init__ 。 碰巧的是,MRO只有一個項目 - 基礎。 所以你真的做了同樣的事情,但是用super()更好的方式(特別是如果你以後進入多重繼承)。


我想了解super()

我們使用super的原因是,可能使用協作多重繼承的子類將在方法解析順序(MRO)中調用正確的下一個父類函數。

在Python 3中,我們可以像這樣調用它:

class ChildB(Base):
    def __init__(self):
        super().__init__() 

在Python 2中,我們需要像這樣使用它:

        super(ChildB, self).__init__()

沒有超級,您使用多重繼承的能力有限:

        Base.__init__(self) # Avoid this.

我在下面進一步解釋。

“這段代碼實際上有什麼區別?:”

class ChildA(Base):
    def __init__(self):
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        super(ChildB, self).__init__()
        # super().__init__() # you can call super like this in Python 3!

這段代碼的主要區別在於你在__init__使用super獲得了一個間接層,它使用當前類來確定要在MRO中查找的下一個類的__init__

我在規範問題的答案中說明了這種差異,如何在Python中使用“超級”? ,它演示了依賴注入協作多重繼承

如果Python沒有super

這裡的代碼實際上與super非常相似(它是如何在C中實現的,減去一些檢查和回退行為,並轉換為Python):

class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()             # Get the Method Resolution Order.
        check_next = mro.index(ChildB) + 1 # Start looking after *this* class.
        while check_next < len(mro):
            next_class = mro[check_next]
            if '__init__' in next_class.__dict__:
                next_class.__init__(self)
                break
            check_next += 1

寫得更像本機Python:

class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()
        for next_class in mro[mro.index(ChildB) + 1:]: # slice to end
            if hasattr(next_class, '__init__'):
                next_class.__init__(self)
                break

如果我們沒有super對象,我們必須在任何地方編寫這個手動代碼(或重新創建它!)以確保我們在方法解析順序中調用正確的下一個方法!

超級如何在沒有明確告知調用它的方法中的哪個類和實例的情況下在Python 3中執行此操作?

它獲取調用堆棧幀,並找到類(隱式存儲為本地自由變量, __class__ ,使調用函數成為類的閉包)和該函數的第一個參數,它應該是通知它的實例或類使用哪種方法解決順序(MRO)。

因為它需要MRO的第一個參數,所以使用super和靜態方法是不可能的

對其他答案的批評:

使用super()可以避免顯式引用基類,這可能很好。 。 但主要優勢在於多重繼承,可以發生各種有趣的事情。 如果您還沒有,請參閱super上的標准文檔。

這是相當浪漫的,並沒有告訴我們太多,但super的要點是不要避免編寫父類。 關鍵是要確保調用方法解析順序(MRO)中的下一個方法。 這在多重繼承中變得很重要。

我會在這裡解釋一下。

class Base(object):
    def __init__(self):
        print("Base init'ed")

class ChildA(Base):
    def __init__(self):
        print("ChildA init'ed")
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        print("ChildB init'ed")
        super(ChildB, self).__init__()

讓我們創建一個我們希望在Child之後調用的依賴項:

class UserDependency(Base):
    def __init__(self):
        print("UserDependency init'ed")
        super(UserDependency, self).__init__()

現在記住, ChildB使用super, ChildA不使用:

class UserA(ChildA, UserDependency):
    def __init__(self):
        print("UserA init'ed")
        super(UserA, self).__init__()

class UserB(ChildB, UserDependency):
    def __init__(self):
        print("UserB init'ed")
        super(UserB, self).__init__()

UserA不會調用UserDependency方法:

>>> UserA()
UserA init'ed
ChildA init'ed
Base init'ed
<__main__.UserA object at 0x0000000003403BA8>

但是UserB ,因為ChildB使用super ,確實!:

>>> UserB()
UserB init'ed
ChildB init'ed
UserDependency init'ed
Base init'ed
<__main__.UserB object at 0x0000000003403438>

對另一個答案的批評

在任何情況下都不應該執行以下操作,這是另一個答案所暗示的,因為當您繼承ChildB時肯定會出錯:

        super(self.__class__, self).__init__() # Don't do this. Ever.

(這個答案並不聰明或特別有趣,但儘管評論中有直接的批評和超過17個贊成票,但回答者堅持提出建議,直到一位善良的編輯解決了他的問題。)

說明:那個回答建議像這樣調用super:

super(self.__class__, self).__init__()

這是完全錯誤的。 super讓我們在子類中查找MRO中的下一個父級(請參閱本答案的第一部分)。 如果你告訴super我們在子實例的方法中,那麼它將在行中查找下一個方法(可能是這個)導致遞歸,可能導致邏輯失敗(在回答者的例子中,它確實)或者當運行錯誤時超出遞歸深度。

>>> class Polygon(object):
...     def __init__(self, id):
...         self.id = id
...
>>> class Rectangle(Polygon):
...     def __init__(self, id, width, height):
...         super(self.__class__, self).__init__(id)
...         self.shape = (width, height)
...
>>> class Square(Rectangle):
...     pass
...
>>> Square('a', 10, 10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
TypeError: __init__() missing 2 required positional arguments: 'width' and 'height'

已經註意到在Python 3.0+中你可以使用

super().__init__()

進行調用,這是簡潔的,並且不需要您明確地引用父OR類名稱,這可能很方便。 我只想在Python 2.7或更低版​​本中添加它,可以通過編寫self.__class__而不是類名來獲得這種名稱不敏感的行為,即

super(self.__class__, self).__init__()

但是,這會破壞對從類繼承的任何類的super調用,其中self.__class__可以返回子類。 例如:

class Polygon(object):
    def __init__(self, id):
        self.id = id

class Rectangle(Polygon):
    def __init__(self, id, width, height):
        super(self.__class__, self).__init__(id)
        self.shape = (width, height)

class Square(Rectangle):
    pass

這裡我有一個類Square ,它是Rectangle一個子類。 假設我不想為Square編寫單獨的構造函數,因為Rectangle的構造函數足夠好,但無論出於什麼原因我想實現Square,所以我可以重新實現其他方法。

當我使用mSquare = Square('a', 10,10)創建Square ,Python調用Rectangle的構造函數,因為我沒有給Square自己的構造函數。 但是,在Rectangle的構造函數中,調用super(self.__class__,self)將返回mSquare的超類,因此它再次調用Rectangle的構造函數。 這就是無限循環的發生方式,正如@S_C所提到的那樣。 在這種情況下,當我運行super(...).__init__()我正在調用Rectangle的構造函數,但由於我沒有給它任何參數,我將得到一個錯誤。



超級沒有副作用

Base = ChildB

Base()

按預期工作

Base = ChildA

Base()

進入無限遞歸。


它只是你已經有了Object.new的對象,但是在使用構造器語法時你還沒有一個對象。







python class oop inheritance super