web-dev-qa-db-ja.com

継承のベストプラクティス:* args、** kwargs、または明示的にパラメーターを指定

親クラスのメソッドを上書きすることがよくあります。指定されたパラメーターを明示的にリストするか、単にブランケット*args, **kwargsコンストラクト。 1つのバージョンは他のバージョンより優れていますか?ベストプラクティスはありますか?欠けている(不利な)利点は何ですか?

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

明示的なバリアントの認識される利点

  • より明示的(Zen of Python)
  • 握りやすい
  • 簡単にアクセスできる関数パラメーター

ブランケットバリアントの認識される利点

  • もっと乾燥
  • 親クラスは簡単に交換可能
  • 親メソッドのデフォルト値の変更は、他のコードに触れることなく伝播されます
41
Maik Hoepfel

リスコフ置換原理

一般に、メソッドシグネチャの派生型が異なることは望ましくありません。これは、派生型の使用を交換する場合に問題を引き起こす可能性があります。これは、多くの場合、 Liskov Substitution Principle と呼ばれます。

明示的署名の利点

同時に、すべてのメソッドが*args**kwargsの署名を持つことは正しいとは思いません。明示的な署名:

  • 適切な引数名でメソッドを文書化するのに役立ちます
  • どの引数が必要で、どの引数にデフォルト値があるかを指定することにより、メソッドを文書化するのに役立ちます
  • 暗黙的な検証を提供します(必要な引数が欠落していると明らかな例外がスローされます)

可変長引数と結合

カップリングを適切に行うために可変長引数を間違えないでください。親クラスと派生クラスの間には一定量の凝集性がなければなりません。そうでなければ、それらは互いに関連しません。関連するコードが結束のレベルを反映する結合をもたらすことは正常です。

可変長引数を使用する場所

可変長引数の使用は、最初のオプションではありません。次のような正当な理由がある場合に使用する必要があります。

  • 関数ラッパー(デコレーター)を定義します。
  • パラメトリック多相関数の定義。
  • 実際に使用できる引数が完全に可変である場合(たとえば、一般化されたDB接続関数)。 DB接続関数は通常、単一引数形式と複数引数形式の両方で、多くの異なる形式で 接続文字列 を取ります。データベースごとに異なるオプションセットもあります。
  • ...

何かおかしいですか?

多くの引数をとるメソッドまたは異なるシグネチャを持つ派生メソッドを作成することが多い場合は、コードの編成方法に大きな問題がある可能性があります。

31
dietbuddha

私の選択は次のとおりです。

class Child(Parent):

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

*argsおよび**kwargsからコミット引数にアクセスすることを避け、Parent:saveの署名が変更された場合(たとえば、新しいデフォルト引数を追加する場合)に安全を保ちます。

更新:この場合、* argsがあると、新しい位置引数が親に追加された場合に問題が発生する可能性があります。 **kwargsのみを保持し、デフォルト値を持つ新しい引数のみを管理します。伝播するエラーを回避します。

14
luc

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

この方法では、署名の変更は子で非常に読みやすく、元の署名は親で非常に読みやすくなります。

私の意見では、これは多重継承がある場合のより良い方法でもあります。なぜなら、superを数回呼び出すのは、引数やkwargsがない場合はかなりうんざりするからです。

価値があるものとしては、これはかなりの数のPython libsとフレームワーク(Django、Tornado、Requests、Markdown、いくつか挙げれば))でも推奨される方法です。そのようなことについて、私はこのアプローチが非常に広く普及していることを暗示しているだけです。

4
dmg

本当の答えではなく、補足的な注意:親クラスのデフォルト値が子クラスに伝播されることを本当に確認したい場合は、次のようなことができます:

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)

しかし、これは非常に見苦しいと認めなければならず、本当に必要だと感じた場合にのみ使用します。

オートコンプリートを使用すると、関数呼び出し中に関数のメソッドシグネチャを確認できるため、明示的な引数を好みます。

2
GeneralBecos

他の答えに加えて:

可変引数を持つと、親が子から「分離」される場合がありますが、作成されたオブジェクトと親の間にカップリングが作成されます。アプリケーション内に複数のオブジェクトを作成する可能性があるため)

デカップリングを探しているなら、 composition over inheritance を見てください

0
JACH