268

私はPythonでのダックタイピングの古い方法に慣れているため、ABC(抽象基本クラス)の必要性を理解できません。ヘルプはそれらを使用する方法について良いです。

PEPで理論的根拠を読み込もうとしましたが、頭を悩ませました。可変シーケンスコンテナを探していた場合は、それをチェックする__setitem__か、おそらくそれを使用しようとします(EAFP)。ABCを使用する数値モジュールの実際の使用法に出くわしたことはありませんが、それは私が理解しなければならない最も近いものです。

誰かが私に理論的根拠を説明できますか?

4

6 に答える 6

289

@Oddthinkingの答えは間違いではありませんが、Pythonがダックタイピングの世界でABCを持っている本当の実際的な理由を見逃していると思います。

抽象メソッドはきちんとしていますが、私の意見では、ダックタイピングでまだカバーされていないユースケースを実際に満たすことはありません。抽象基本クラスの真の力は、との動作をカスタマイズできるようにする方法にisinstanceありissubclassます。(__subclasshook__基本的には、Python__instancecheck____subclasscheck__フックに加えてより使いやすいAPIです。)組み込みの構造をカスタムタイプで機能するように適合させることは、Pythonの哲学の大部分を占めています。

Pythonのソースコードは模範的なものです。標準ライブラリでの定義方法は次のとおりcollections.Containerです(執筆時点)。

class Container(metaclass=ABCMeta):
    __slots__ = ()

    @abstractmethod
    def __contains__(self, x):
        return False

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Container:
            if any("__contains__" in B.__dict__ for B in C.__mro__):
                return True
        return NotImplemented

この定義は__subclasshook__、属性を持つクラスは、__contains__直接サブクラス化されていなくても、Containerのサブクラスと見なされることを示しています。だから私はこれを書くことができます:

class ContainAllTheThings(object):
    def __contains__(self, item):
        return True

>>> issubclass(ContainAllTheThings, collections.Container)
True
>>> isinstance(ContainAllTheThings(), collections.Container)
True

言い換えれば、適切なインターフェースを実装すれば、あなたはサブクラスになります!ABCは、ダックタイピングの精神に忠実でありながら、Pythonでインターフェイスを定義する正式な方法を提供します。さらに、これはオープンクローズ原則を尊重する方法で機能します。

Pythonのオブジェクトモデルは、表面的にはより「従来の」オブジェクト指向システム(Java *を意味します)のモデルと似ていますが、Yerクラス、Yerオブジェクト、Yerメソッドがありますが、表面をスクラッチすると、はるかに豊富なものが見つかります。より柔軟に。同様に、Pythonの抽象基本クラスの概念はJava開発者には認識できるかもしれませんが、実際には、それらは非常に異なる目的を目的としています。

単一のアイテムまたはアイテムのコレクションに作用できるポリモーフィック関数を書いていることに気付くことがありますが、同等のブロックisinstance(x, collections.Iterable)よりもはるかに読みやすいことがわかります。(Pythonを知らなかった場合、コードの意図を最も明確にするのはこれら3つのうちどれですか?)hasattr(x, '__iter__')try...except

そうは言っても、自分でABCを作成する必要はめったになく、通常はリファクタリングを通じてその必要性を発見します。多くの属性チェックを行うポリモーフィック関数、または同じ属性チェックを行う多くの関数を見ると、その匂いは抽出されるのを待っているABCの存在を示唆しています。

*Javaが「従来の」オブジェクト指向システムであるかどうかについての議論に入る必要はありません...


補遺:抽象基本クラスはisinstanceとの動作をオーバーライドできますが、issubclassそれでも仮想サブクラスのMROには入りません。isinstance(x, MyABC) == Trueこれは、クライアントにとって潜在的な落とし穴です。で定義されたメソッドを持つすべてのオブジェクトではありませんMyABC

class MyABC(metaclass=abc.ABCMeta):
    def abc_method(self):
        pass
    @classmethod
    def __subclasshook__(cls, C):
        return True

class C(object):
    pass

# typical client code
c = C()
if isinstance(c, MyABC):  # will be true
    c.abc_method()  # raises AttributeError

残念ながら、これは「それをしない」トラップの1つです(Pythonには比較的少ないです!):a__subclasshook__メソッドと非抽象メソッドの両方でABCを定義することは避けてください。さらに、__subclasshook__ABCが定義する一連の抽象メソッドと一貫性のある定義を作成する必要があります。

于 2013-10-11T22:20:29.890 に答える
190

短縮版

ABCは、クライアントと実装されたクラスの間でより高いレベルのセマンティックコントラクトを提供します。

ロングバージョン

クラスとその呼び出し元の間には契約があります。クラスは特定のことを行い、特定のプロパティを持つことを約束します。

契約にはさまざまなレベルがあります。

非常に低いレベルでは、コントラクトにメソッドの名前またはそのパラメーターの数が含まれる場合があります。

静的に型付けされた言語では、そのコントラクトは実際にはコンパイラーによって強制されます。Pythonでは、EAFPを使用するか、introspectionと入力して、不明なオブジェクトがこの予想される契約を満たしていることを確認できます。

しかし、契約には、より高いレベルのセマンティックな約束もあります。

たとえば、__str__()メソッドがある場合、オブジェクトの文字列表現を返すことが期待されます。オブジェクトのすべてのコンテンツを削除し、トランザクションをコミットして、プリンターから空白のページを吐き出す可能性があります...しかし、Pythonマニュアルで説明されているように、オブジェクトが何をすべきかについての一般的な理解があります。

これは特殊なケースであり、セマンティックコントラクトがマニュアルに記載されています。メソッドは何をすべきprint()ですか?オブジェクトをプリンターに書き込むか、行を画面に書き込むか、または他の何かを書き込む必要がありますか?それは異なります-あなたはここで完全な契約を理解するためにコメントを読む必要があります。メソッドが存在することを単にチェックするクライアントコードのprint()一部は、コントラクトの一部を確認しました-メソッド呼び出しを行うことができますが、呼び出しのより高いレベルのセマンティクスに合意があることは確認していません。

抽象基本クラス(ABC)の定義は、クラスの実装者と呼び出し元の間でコントラクトを作成する方法です。これは、メソッド名のリストだけでなく、それらのメソッドが何をすべきかについての共通の理解です。このABCから継承する場合は、print()メソッドのセマンティクスを含め、コメントに記載されているすべてのルールに従うことを約束します。

Pythonのダックタイピングには、静的タイピングよりも柔軟性に多くの利点がありますが、すべての問題を解決できるわけではありません。ABCは、Pythonの自由形式と、静的に型付けされた言語の束縛と規律の間の中間ソリューションを提供します。

于 2010-08-26T03:59:12.207 に答える
129

ABCの便利な機能は、必要なすべてのメソッド(およびプロパティ)を実装しないと、AttributeError実際に欠落しているメソッドを使用しようとしたときに、場合によってはかなり後でではなく、インスタンス化時にエラーが発生することです。

from abc import ABCMeta, abstractmethod

# python2
class Base(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def foo(self):
        pass

    @abstractmethod
    def bar(self):
        pass

# python3
class Base(object, metaclass=ABCMeta):
    @abstractmethod
    def foo(self):
        pass

    @abstractmethod
    def bar(self):
        pass

class Concrete(Base):
    def foo(self):
        pass

    # We forget to declare `bar`


c = Concrete()
# TypeError: "Can't instantiate abstract class Concrete with abstract methods bar"

https://dbader.org/blog/abstract-base-classes-in-pythonからの例

編集:python3構文を含めるために、@PandasRocksに感謝します

于 2015-05-19T14:46:33.100 に答える
21

これにより、プロトコル内のすべてのメソッドの存在を確認したり、サポートされていないために「敵」の領域の奥深くで例外をトリガーしたりすることなく、オブジェクトが特定のプロトコルをサポートするかどうかを判断できます。

于 2010-08-25T22:58:14.293 に答える
11

抽象メソッドは、親クラスで呼び出しているメソッドが子クラスに表示される必要があることを確認します。以下は、abstractを呼び出して使用する通常の方法です。python3で書かれたプログラム

通常の呼び出し方法

class Parent:
    def methodone(self):
        raise NotImplemented()

    def methodtwo(self):
        raise NotImplementedError()

class Son(Parent):
   def methodone(self):
       return 'methodone() is called'

c = Son()
c.methodone()

'methodone()が呼び出されます'

c.methodtwo()

NotImplementedError

抽象メソッドを使用

from abc import ABCMeta, abstractmethod

class Parent(metaclass=ABCMeta):
    @abstractmethod
    def methodone(self):
        raise NotImplementedError()
    @abstractmethod
    def methodtwo(self):
        raise NotImplementedError()

class Son(Parent):
    def methodone(self):
        return 'methodone() is called'

c = Son()

TypeError:抽象メソッドmethodtwoで抽象クラスSonをインスタンス化できません。

methodtwoは子クラスで呼び出されないため、エラーが発生しました。適切な実装は以下のとおりです

from abc import ABCMeta, abstractmethod

class Parent(metaclass=ABCMeta):
    @abstractmethod
    def methodone(self):
        raise NotImplementedError()
    @abstractmethod
    def methodtwo(self):
        raise NotImplementedError()

class Son(Parent):
    def methodone(self):
        return 'methodone() is called'
    def methodtwo(self):
        return 'methodtwo() is called'

c = Son()
c.methodone()

'methodone()が呼び出されます'

于 2018-07-19T06:48:47.503 に答える
1

ABCを使用すると、デザインパターンとフレームワークを作成できます。BrandonRhodesによるこのpyconトークをご覧ください。

Pythonデザインパターン1

Python自体のプロトコル(イテレータ、デコレータ、スロット(FlyWeightパターンを実装する)は言うまでもありません)はすべて、ABC(CPythonでは仮想メソッド/クラスとして実装されていますが)のおかげで可能です。

ダックタイピングは、Pythonでいくつかのパターンを簡単にします。これは、ブランドンが言及していますが、アダプターなど、他の多くのパターンが引き続きポップアップし、Pythonで役立ちます。

つまり、ABCを使用すると、スケーラブルで再利用可能なコードを記述できます。GoFによると:

  1. 実装ではなく、インターフェースへのプログラミング(継承はカプセル化を破ります。インターフェースへのプログラミングは、制御の疎結合/反転/「ハリウッドの原則:私たちに電話しないでください。私たちはあなたに電話します」を促進します)

  2. クラス継承よりもオブジェクトコンポジションを優先する(作業を委任する)

  3. 変化する概念をカプセル化します(オープンクローズの原則により、クラスは拡張のためにオープンになりますが、変更のためにクローズされます)

さらに、Python(たとえば)の静的型チェッカーの出現により、関数が引数として受け入れるか返すすべてのオブジェクトのmypy代わりに、ABCを型として使用できるようになりました。Union[...]コードベースが新しいオブジェクトをサポートするたびに、実装ではなく型を更新する必要があると想像してみてください。それは非常に速く保守不可能になります(スケーリングしません)。

于 2021-11-06T23:33:20.580 に答える