簡體   English   中英

Python 多個 Inheritance:對所有調用 super

[英]Python Multiple Inheritance: call super on all

我有以下兩個超類:

class Parent1(object):
    def on_start(self):
        print('do something')

class Parent2(object):
    def on_start(self):
        print('do something else')

我想要一個孩子 class 繼承自兩者都能夠為父母雙方調用超級。

class Child(Parent1, Parent2):
    def on_start(self):
        # super call on both parents

執行此操作的 Pythonic 方法是什么? 謝謝。

執行摘要:

Super僅基於類層次結構的__mro__執行一種方法。 如果要使用同一個名稱執行多個方法,則需要編寫父類以協作方式執行此操作(通過隱式或顯式調用super ),或者需要遍歷子類的__bases____mro__值。

super的工作是將方法調用的部分或全部委派給類祖先樹中的某些現有方法。 委托可能超出您控制的類。 委托的方法名稱必須存在於基類組中。

下面介紹的使用__bases__try/except方法最接近於您如何調用同一個父對象的同名方法的完整答案。


在要調用父級方法之一但不知道哪個父級的情況下, super很有用:

class Parent1(object):
    pass

class Parent2(object):
    # if Parent 2 had on_start - it would be called instead 
    # because Parent 2 is left of Parent 3 in definition of Child class
    pass

class Parent3(object):
    def on_start(self):
        print('the ONLY class that has on_start')        

class Child(Parent1, Parent2, Parent3):
    def on_start(self):
        super(Child, self).on_start()

在這種情況下, Child有三個直系父母。 只有一個Parent3具有on_start方法。 調用super Parent3只有Parent3具有on_start並且這就是被調用的方法。

如果Child繼承自具有on_start方法的多個類,則該順序從左到右(在類定義中列出)和從下到上(作為邏輯繼承)解析。 在類的層次結構中,只有一個方法被調用,而同名的其他方法已被替換。

因此,更常見的是:

class GreatGrandParent(object):
    pass

class GrandParent(GreatGrandParent):
    def on_start(self):
        print('the ONLY class that has on_start')

class Parent(GrandParent):
    # if Parent had on_start, it would be used instead
    pass        

class Child(Parent):
    def on_start(self):
        super(Child, self).on_start()

如果要通過方法名稱調用多個父方法,可以在這種情況下使用__bases__而不是super並遍歷Child的基類,而無需按名稱知道這些類:

class Parent1(object):
    def on_start(self):
        print('do something')

class Parent2(object):
    def on_start(self):
        print('do something else')

class Child(Parent1, Parent2):
    def on_start(self):
        for base in Child.__bases__:
            base.on_start(self)

>>> Child().on_start()
do something
do something else

如果有可能,其中一個基類沒有on_start ,則可以使用try/except:

class Parent1(object):
    def on_start(self):
        print('do something')

class Parent2(object):
    def on_start(self):
        print('do something else')

class Parent3(object):
    pass        

class Child(Parent1, Parent2, Parent3):
    def on_start(self):
        for base in Child.__bases__:
            try:
                base.on_start(self)
            except AttributeError:
                # handle that one of those does not have that method
                print('"{}" does not have an "on_start"'.format(base.__name__))

>>> Child().on_start()
do something
do something else
"Parent3" does not have an "on_start"

使用__bases__行為類似於super但是對於Child定義中定義的每個類層次結構。 即,它將通過每個on_start類,直到on_start類的每個父級滿足一次 on_start為止:

class GGP1(object):
    def on_start(self):
        print('GGP1 do something')

class GP1(GGP1):
    def on_start(self):
        print('GP1 do something else')

class Parent1(GP1):
    pass        

class GGP2(object):
    def on_start(self):
        print('GGP2 do something')

class GP2(GGP2):
    pass

class Parent2(GP2):
    pass            

class Child(Parent1, Parent2):
    def on_start(self):
        for base in Child.__bases__:
            try:
                base.on_start(self)
            except AttributeError:
                # handle that one of those does not have that method
                print('"{}" does not have an "on_start"'.format(base.__name__))

>>> Child().on_start()
GP1 do something else
GGP2 do something
# Note that 'GGP1 do something' is NOT printed since on_start was satisfied by 
# a descendant class L to R, bottom to top

現在想象一個更復雜的繼承結構:

在此處輸入圖片說明

如果您想要每個on_starton_start方法,則可以使用__mro__並篩選出不包含on_start的類作為該類的__dict__一部分。 否則,您將有可能獲得祖先的on_start方法。 換句話說, hassattr(c, 'on_start')True對每個類Child是從(除了后代object在這種情況下),因為Ghengis具有on_start屬性和所有的類都是從Ghengis派生類。

**警告-僅演示**

class Ghengis(object):
    def on_start(self):
        print('Khan -- father to all')    

class GGP1(Ghengis):
    def on_start(self):
        print('GGP1 do something')

class GP1(GGP1):
    pass

class Parent1(GP1):
    pass        

class GGP2(Ghengis):
    pass

class GP2(GGP2):
    pass

class Parent2(GP2):
    def on_start(self):
        print('Parent2 do something')

class Child(Parent1, Parent2):
    def on_start(self):
        for c in Child.__mro__[1:]:
            if 'on_start' in c.__dict__.keys():
                c.on_start(self)

>>> Child().on_start()
GGP1 do something
Parent2 do something
Khan -- father to all

但這也有一個問題-如果將Child進一步子類化,則Child的子對象也會在同一__mro__鏈上循環。

正如Raymond Hettinger所說:

super()用於將方法調用委派給實例的祖先樹中的某個類。 為了使可重排序的方法調用起作用,需要協同設計類。 這提出了三個容易解決的實際問題:

1)super()調用的方法必須存在

2)主叫方和被叫方需要具有匹配的參數簽名,並且

3)方法的每次出現都需要使用super()

解決方案是編寫在祖先列表中統一使用super合作類,或者創造性地使用適配器模式來適應您無法控制的類。 Python的super()文章super!中更全面地討論了這些方法 由Raymond Hettinger撰寫。

class Parent1(object):
    def on_start(self):
        print('do something')

class Parent2(object):
    def on_start(self):
        print('do something else')

class Child(Parent1, Parent2):
    def on_start(self):
        super(Child, self).on_start()
        super(Parent1, self).on_start()

c = Child()

c.on_start()
do something
do something else

或沒有超級:

class Child(Parent1, Parent2):
    def on_start(self):
        Parent1.on_start(self)
        Parent2.on_start(self)

在您的情況下,由於兩個父對象都實現了相同的方法,因此super將從左到右與繼承的第一個父對象相同(對於您的代碼, Parent1 )。 super調用兩個函數是不可能的。 要執行所需的操作,必須簡單地從父類中調用方法,如下所示:

class Child(Parent1, Parent2):
    def on_start (self):
        Parent1.on_start()
        Parent2.on_start()

super()調用是為了合作。

一個標准的成語如下:

  • 一切最終都繼承自一個可以丟棄不必要的關鍵字 arguments 的基礎(以避免將它們轉發到object.__init__時發生異常)。
  • 每個class 調用super() ,包括基數。
  • 每個__init__接受**kwargs 根據特定的關鍵字初始化本地的東西; 然后沿着super鏈傳遞信息。
  • 調用代碼將傳遞初始化鏈中每個 class 所需的關鍵字參數。

所以,例如:

class Person:
    def __init__(self, **kwargs):
        print(f'Initializing a Person')
        pass

class Parent1(Person):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.heirloom1 = kwargs.get('heirloom1', None)
        print(f'Set up Parent1 of the {self.__class__} with {self.heirloom1!r}')

class Parent2(Person):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.heirloom2 = kwargs.get('heirloom2', None)
        print(f'Set up Parent2 of the {self.__class__} with {self.heirloom2!r}')

class Child(Parent1, Parent2):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.inheritance = kwargs.get('inheritance', None)

spoiled_rotten = Child(inheritance='mansion', heirloom1='trust fund', heirloom2='sports car')

特別注意,當spoiled_rotten被創建時, super().__init__(**kwargs)將轉發給Parent2 ,而不是Person ,並且Person.__init__只被調用一次。 super並不意味着“基類”; 它的意思是“ self的 MRO 中的下一個 class”——而self在整個過程中都是一個Child實例。

暫無
暫無

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

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