2015-12-11 106 views
16

我很好奇是否有一種方法在Python中強制(從父類)父級方法從子級調用,當它被覆蓋。強制子類調用父級方法時重寫它

例子:

class Parent(object): 

    def __init__(self): 
     self.someValue=1 

    def start(self): 
     ''' Force method to be called''' 
     self.someValue+=1 

正確執行的是什麼,我想我的孩子子類來實現將是:

class ChildCorrect(Parent): 

    def start(self): 
     Parent.start(self) 
     self.someValue+=1 

但是,有沒有辦法強制將開發兒童類開發商特別呼叫(不只是覆蓋)Parent類中定義的'start'方法,以防忘記調用它:

class ChildIncorrect(Parent): 

    def start(self): 
     '''Parent method not called, but correctly overridden''' 
     self.someValue+=1 

另外,如果這不被認爲是最佳實踐,那麼會有什麼替代?

+0

重寫方法在調用超類時是否必須有自由*?即如果重寫方法在執行的開始或結束時被強制調用超級方法,它會好嗎? (可能在一般情況下限制它是不行的。)如果是這樣,可以使用元類來包裝任何重寫方法並隱式調用super;那麼重寫方法將不被允許自行調用超級方法(您可能無法執行)。 –

+0

暗示用戶應該調用超類方法,類似於缺少抽象方法實現的方式,用linter拾取? – pnovotnak

回答

14

如何(不)做

沒有,有沒有安全的辦法強迫用戶調用超。讓我們回顧幾個可以達到或類似目標的選項,並討論爲什麼這是一個糟糕的主意。在下一節中,我還將討論如何理解(關於Python社區)處理這種情況的方式。

  1. 甲元類可以檢查,在時間子類被定義,一個方法是否覆蓋目標方法(=具有相同的名稱作爲目標法)超級用適當的參數來調用。

    這需要深入實施的行爲,例如使用CPython的dis模塊。在任何情況下,我都不能想象這是一個好主意 - 它取決於CPython的特定版本以及您使用CPython的事實。

  2. 元類可以與基類共同操作。在這種情況下,基類會在調用繼承方法時通知元類,並且元類用任意一個守護代碼包裝任何覆蓋方法。

    守護代碼測試在執行重寫方法期間是否調用了繼承的方法。這有一個缺點,就是每個需要這個「特徵」的方法都需要一個單獨的通知通道,此外還需要考慮線程安全和重新進入。另外,覆蓋方法在您注意到它沒有調用繼承方法的地方完成執行,根據您的場景,這可能很糟糕。

    它也提出了一個問題:如果重寫方法沒有調用繼承方法,該怎麼辦?使用該方法的代碼引發異常可能是意想不到的(或者更糟糕的是,它可能會假設該方法根本沒有效果,這是不正確的)。

    此外,遲到的反饋意見是開發人員壓倒班級(如果他們得到這種反饋的話)是不好的。

  3. 元類可以爲每個重寫方法生成一段保護代碼,它在執行覆蓋方法之前或之後自動調用繼承方法

    這裏的缺點是開發人員不會期待繼承的方法被自動調用並且無法停止該調用(例如,如果不滿足子類的特殊前提條件)或者在自己的覆蓋方法中進行控制繼承的方法被調用。編碼python(避免意外,顯式優於隱式,可能更多)時,這違反了一大堆明智的原則。

  4. 點2和3從點2使用鹼基和元類的共同操作的組合,從點3所述保護代碼可以被擴展到自動調用超IFF壓倒一切的方法還沒有稱爲超他們自己。

    同樣,這是意想不到的,但它解決了重複超級調用的問題以及如何處理不調用超級的方法。

    但是,仍然存在問題。儘管線程安全可以通過線程本地來解決,但是當不滿足前提條件時,重載方法仍然無法中止超級調用,除非引發了所有情況下可能不需要的異常。另外,super只能在之後自動調用,而不是在之前,這在某些情況下也是不受歡迎的。

此外,沒有針對重新結合對象和類的壽命期間的屬性,儘管這可以通過使用描述符和/或延伸的元類來照顧它可以幫助這個幫助。

爲什麼不這樣做

而且,如果這還不算是最好的做法,這將是一個選擇嗎?

Python中常見的最佳做法是假定你是成年人之間自願的人。這意味着,除非您允許,否則沒有人會積極地嘗試爲您的代碼做惡劣的事情。在這樣的生態系統中,在方法或類的文檔中添加.. warning::是有意義的,以便從該類繼承的任何人都知道他們必須做什麼。

此外,在適當的位置調用超級方法在許多情況下都有意義,因此開發人員使用基類會反過來考慮它,只是偶然忘記它。在這種情況下,使用上面第三點的元類無助於兩個用戶都不得不記得而不是來調用超級,這可能是一個問題,特別是對於有經驗的程序員。

它也違反了最小驚喜的原則,並且「顯式比隱式更好」(noone期望隱式調用繼承的方法!)。這也必須記錄良好,在這種情況下,你也可以求助於不自動調用超文本文件,它使得比平常更有意義來調用繼承的方法。

-1

所有你需要的是super

有了它,你的代碼看起來是這樣的:

class Parent(object): 
    def __init__(self): 
     self.someValue = 1 

    def start(self): 
     self.someValue += 1 


class Child(Parent): 
    def start(self): 
     # put code here 
     super().start() 
     # or here 
+2

感謝您的回覆!然而,我所指的是,是否有任何我可以對父類中定義的方法執行的操作,即使它被覆蓋,也會強制它被調用。如果開發人員不會調用這個,會發生什麼情況:super(Child,self).start() – Spektor

+0

呃,我誤解了你。所以,我認爲在Python中通過構建繼承是不可能的 - 你可以覆蓋每個函數,所以不能保證你的函數會被調用。 雖然,Python約定名稱以單下劃線開頭的方法/屬性(例如_my_method)是「受保護的」,您使用它們的風險自負。 – andrzej3393

1

但是,有沒有辦法強制將開發兒童類 開發商特別要求(不只是覆蓋)「開始」方法在 中定義的父類在他們忘記調用的情況下

在Python中,您無法控制其他人如何覆蓋您的函數。

+1

檢查您是否可以爲此需求使用「元類」。 –

+1

元類不會有任何幫助。在這種情況下,它可以做的最有用的事情是檢測到該函數已被覆蓋。儘管如此,這還不夠。你必須檢查它是否調用了超級適配器(如果不依賴實現定義的行爲,如CPython中的「dis」模塊或實際調用方法並跟蹤調用)在實例的生命週期中分配。在調用方法時跟蹤調用可以使用元類來完成,但肯定是矯枉過正。 –

2

Python中很少有關於迫使其他程序員以某種方式編碼的問題。即使在標準庫中,你會發現警告such as this

如果子類覆蓋的構造,它必須確保做其他事情的線程之前調用基類構造函數。

雖然有可能使用元類來修改子類的行爲,但在這種情況下最好的做法是用另一種方法替換新的方法,方法和原始方法。然而,如果程序員確實在在新代碼中調用了父方法,那麼這會產生問題,再加上會導致不應該在應該調用父方法的壞習慣。

換句話說,Python不是以這種方式構建的。

+0

感謝您的解釋;這正是我想要了解的內容,而且我當然不想實施一些不是pythonic的東西。我想也許,也許會有一種隱藏類型的方法,我可以用來強制執行此操作。 – Spektor

2

對於一些元類黑客,您可以在運行時檢測父啓動是否已被調用。不過我肯定會推薦而不是在實際情況下使用這個代碼,它可能在很多方面都有問題,而且執行這些限制並不是pythonic。

class ParentMetaClass(type): 

    parent_called = False 

    def __new__(cls, name, bases, attrs): 
     print cls, name, bases, attrs 
     original_start = attrs.get('start') 
     if original_start: 
      if name == 'Parent': 
       def patched_start(*args, **kwargs): 
        original_start(*args, **kwargs) 
        cls.parent_called = True 

      else: 
       def patched_start(*args, **kwargs): 
        original_start(*args, **kwargs) 
        if not cls.parent_called: 
         raise ValueError('Parent start not called') 
        cls.parent_called = False 

      attrs['start'] = patched_start 

     return super(ParentMetaClass, cls).__new__(cls, name, bases, attrs) 


class Parent(object): 
    __metaclass__ = ParentMetaClass 

    def start(self): 
     print 'Parent start called.' 


class GoodChild(Parent): 
    def start(self): 
     super(GoodChild, self).start() 
     print 'I am a good child.' 


class BadChild(Parent): 
    def start(self): 
     print 'I am a bad child, I will raise a ValueError.' 
5

如果類層次是你的控制之下,你可以用什麼四(伽馬等)設計模式的剛書調用模板方法模式:

class MyBase: 
    def MyMethod(self): 
     # place any code that must always be called here... 
     print "Base class pre-code" 

     # call an internal method that contains the subclass-specific code 
     self._DoMyMethod() 

     # ...and then any additional code that should always be performed 
     # here. 
     print "base class post-code!" 

    def _DoMyMethod(self): 
     print "BASE!" 


class MyDerived(MyBase): 
    def _DoMyMethod(self): 
     print "DERIVED!" 


b = MyBase() 
d = MyDerived() 

b.MyMethod() 
d.MyMethod() 

輸出:

Base class pre-code 
BASE! 
base class post-code! 
Base class pre-code 
DERIVED! 
base class post-code! 
+0

然後記錄私有方法是要覆蓋的方法。 +1技術,但我建議不要實際使用它。 ;) –

+0

@EthanFurman嗯,爲什麼?對於需要運行的代碼前後代碼來說,這似乎是一個明智的用例(儘管原始問題並不常見)。這裏有更好的成語嗎? –

+0

@JonasWielicki:這是一個明智的用例,但這不是OP所需要的。爲了適應OP的情況,它仍然需要記錄,在這種情況下,你可能只需要記錄父方法需要被調用。 –