簡體   English   中英

Inheritance 最佳實踐:*args、**kwargs 或顯式指定參數 [關閉]

[英]Inheritance best practice : *args, **kwargs or explicitly specifying parameters [closed]

我經常發現自己覆蓋了父 class 的方法,並且永遠無法決定我是應該顯式列出給定參數還是只使用一攬子*args, **kwargs構造。 一個版本比另一個好嗎? 有最佳實踐嗎? 我缺少什么(不利)優勢?

class Parent(object):

    def save(self, commit=True):
        # ...

class Explicit(Parent):

    def save(self, commit=True):
        super(Explicit, self).save(commit=commit)
        # more logic

class Blanket(Parent):

    def save(self, *args, **kwargs):
        super(Blanket, self).save(*args, **kwargs)
        # more logic

顯式變體的感知好處

  • 更明確(Python之禪)
  • 更容易掌握
  • function參數輕松獲取

一攬子變體的感知好處

  • 更干
  • 父母 class 很容易互換
  • 在不觸及其他代碼的情況下傳播父方法中默認值的更改

利斯科夫替代原則

通常,您不希望方法簽名在派生類型中有所不同。 如果要交換派生類型的使用,這可能會導致問題。 這通常被稱為Liskov替代原則

顯式簽名的好處

與此同時,我認為你的所有方法都不具備*args**kwargs的簽名是正確的。 明確簽名:

  • 通過良好的參數名稱幫助記錄方法
  • 通過指定哪些args是必需的以及哪些具有默認值來幫助記錄方法
  • 提供隱式驗證(缺少必需的args拋出明顯的異常)

可變長度參數和耦合

不要將變長參數誤認為是良好的耦合實踐。 父類和派生類之間應該有一定的內聚力,否則它們就不會彼此相關。 相關代碼導致耦合反映內聚水平是正常的。

使用可變長度參數的位置

使用可變長度參數不應該是您的第一選擇。 當你有充分的理由時應該使用它:

  • 定義函數包裝器(即裝飾器)。
  • 定義參數化多態函數。
  • 當您可以采用的參數確實是完全可變的(例如,通用的DB連接函數)。 數據庫連接函數通常采用多種不同形式的連接字符串 ,包括單個arg形式和多arg形式。 不同的數據庫也有不同的選項集。
  • ...

你做錯了什么嗎?

如果您發現自己經常創建帶有許多參數的方法或帶有不同簽名的派生方法,那么您在編寫代碼時可能會遇到更大的問題。

我的選擇是:

class Child(Parent):

    def save(self, commit=True, **kwargs):
        super(Child, self).save(commit, **kwargs)
        # more logic

它避免了從*args**kwargs訪問commit參數,如果Parent:save的簽名Parent:save更改(例如添加新的默認參數),它可以保證安全。

更新 :在這種情況下,如果將新的位置參數添加到父級,則使用* args會導致麻煩。 我只保留**kwargs並僅使用默認值管理新參數。 它可以避免傳播錯誤。

如果你確定Child會保留簽名,那么肯定是明確的方法,但是當Child改變簽名時我個人更喜歡使用這兩種方法:

class Parent(object):
    def do_stuff(self, a, b):
        # some logic

class Child(Parent):
    def do_stuff(self, c, *args, **kwargs):
        super(Child, self).do_stuff(*args, **kwargs)
        # some logic with c

這樣,簽名中的更改在Child中非常易讀,而原始簽名在Parent中非常易讀。

在我看來,當你有多重繼承時,這也是更好的方法,因為當你沒有args和kwargs時,調用super幾次是非常惡心的。

對於它的價值,這也是很多Python庫和框架(Django,Tornado,Requests,Markdown等)的首選方式。 雖然不應該根據這些事情做出選擇,但我只是暗示這種做法非常普遍。

不是一個答案,而是一個側面說明:如果你真的,真的想確保父類的默認值傳播到子類,你可以做類似的事情:

class Parent(object):

    default_save_commit=True
    def save(self, commit=default_save_commit):
        # ...

class Derived(Parent):

    def save(self, commit=Parent.default_save_commit):
        super(Derived, self).save(commit=commit)

但是我不得不承認這看起來很難看,如果我覺得我真的需要它,我只會使用它。

我更喜歡顯式參數,因為自動完成允許您在進行函數調用時查看函數的方法簽名。

除了其他答案:

擁有變量參數可能會將父項與子項“解耦”,但會在創建的對象和父項之間創建耦合,我認為這更糟糕,因為現在你創建了一個“長距離”情侶(更難以發現,更難以維護,因為您可以在應用程序中創建多個對象)

如果您正在尋找脫鈎,請查看合成而不是繼承

為什么沒有人指出 Parent class save() 方法未實現*args and **kwargs的事實,如果Blanket的實例嘗試使用更多關鍵字或位置 arguments 調用 save 方法,這將導致錯誤。

暫無
暫無

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

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