質問

私はPythonでアヒルタイピングの古い方法に慣れているため、ABCの必要性を理解していません(抽象的なベースクラス)。 ヘルプ それらの使用方法には良いです。

私はその理論的根拠を読もうとしました ペップ, 、しかし、それは私の頭を越えました。可変シーケンスコンテナを探していた場合、私はチェックします __setitem__, 、またはおそらくそれを使用しようとします(EAFP)。私は実際の生活に出くわしていません 数字 ABCを使用するモジュールですが、それは私が理解するために最も近いものです。

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

役に立ちましたか?

解決

短縮版

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

ロングバージョン

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

契約には異なるレベルがあります。

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

静的な型の言語では、その契約は実際にコンパイラによって実施されます。 Pythonでは、EAFPまたは内省を使用して、未知のオブジェクトがこの予想される契約を満たしていることを確認できます。

しかし、契約には高レベルの意味的な約束もあります。

たとえば、aがあります __str__() 方法、オブジェクトの文字列表現を返すことが期待されます。それ たぶん......だろう オブジェクトのすべてのコンテンツを削除し、トランザクションをコミットし、プリンターから空白ページを吐き出します...しかし、Pythonマニュアルで説明されていることについては、それが何をすべきかについてよく理解しています。

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

抽象的な基本クラス(ABC)を定義することは、クラスの実装者と発信者の間で契約を作成する方法です。それは単なるメソッド名のリストではなく、それらのメソッドが何をすべきかを共有することです。このABCから継承する場合、コメントに記載されているすべてのルールに従うことを約束しています。 print() 方法。

Pythonのアヒルタイピングには、静的タイピングよりも柔軟性に多くの利点がありますが、すべての問題は解決しません。 ABCは、Pythonのフリーフォームと静的なタイプの言語のボンデージアンドディスカップラインとの間の中間ソリューションを提供します。

他のヒント

@Oddthinkingの答えは間違っていませんが、私はそれが見逃していると思います 本物, 実用的 Reason Pythonには、アヒルタイピングの世界にABCがあります。

抽象的な方法はきちんとしていますが、私の意見では、彼らは実際にはアヒルのタイピングでカバーされていないユースケースを実際に埋めていません。抽象的なベースクラスの本当の力があります 彼らがあなたがの動作をカスタマイズすることを可能にする方法 isinstanceissubclass. (__subclasshook__ 基本的にPythonの上にあるより友好的なAPIです __instancecheck____subclasscheck__ フック。)カスタムタイプに取り組むために組み込まれたコンストラクトを調整することは、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__ aを含むクラスはと言います __contains__ 属性は、直接サブクラス化しなくても、コンテナのサブクラスと見なされます。だから私はこれを書くことができます:

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

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

言い換えると、 適切なインターフェイスを実装すると、サブクラスです! ABCは、アヒルタイピングの精神に忠実でありながら、Pythonでインターフェイスを定義する正式な方法を提供します。それに加えて、これは オープンクロップ原理.

Pythonのオブジェクトモデルは、より「伝統的な」OOシステムの表面的には表面的に似ています(これはJava*を意味します*) - クラス、YERオブジェクト、YERメソッドを手に入れましたが、表面を掻くと、はるかに豊かなものが見つかります。より柔軟です。同様に、Pythonの抽象的な基本クラスの概念は、Java開発者に認識できる場合がありますが、実際には非常に異なる目的を目的としています。

私は時々自分が単一のアイテムまたはアイテムのコレクションに作用できる多型機能を書いていることに気づき、私は見つけます isinstance(x, collections.Iterable) より読みやすくなります hasattr(x, '__iter__') または同等のもの try...except ブロック。 (Pythonを知らなかった場合、これら3つのうちどれがコードの意図を最も明確にするでしょうか?)

とはいえ、私は自分のABCを書く必要はめったになく、通常、リファクタリングを通じて必要性を発見します。多型関数が多くの属性チェックを作成したり、同じ属性チェックを作成する多くの機能を作成したりすると、その匂いが抽出されるのを待っているABCの存在を示唆しています。

*Javaが「伝統的な」OOシステムであるかどうかについて議論することなく...


補遺: :抽象的なベースクラスがの動作をオーバーライドできる場合でも isinstanceissubclass, 、まだ入力していません 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

残念ながら、これらの「Just Do dot That」の1つ(Pythonには比較的少ない!):両方のABCを定義することは避けてください __subclasshook__ および非抽象メソッド。さらに、定義を作成する必要があります __subclasshook__ ABCが定義する一連の抽象的なメソッドと一致しています。

ABCSの便利な機能は、必要なすべてのメソッド(およびプロパティ)を実装しない場合、インスタンス化時にエラーが発生しないことです。 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に感謝します

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

抽象的方法親クラスで呼び出している方法を子供クラスに表示する必要があることを確認してください。以下は、要約を呼び出して使用するNORAMLの方法です。 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()

タイプエラー:抽象的なメソッドを備えた抽象クラスの息子をインスタンス化することはできません。

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()は呼び出されます

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top