回答:
ABCは、クライアントと実装されたクラスの間に、より高いレベルのセマンティックコントラクトを提供します。
クラスとその呼び出し元の間には契約があります。クラスは、特定のことを実行し、特定のプロパティを持つことを約束します。
契約にはさまざまなレベルがあります。
非常に低いレベルでは、コントラクトにはメソッドの名前またはそのパラメーターの数が含まれる場合があります。
静的に型付けされた言語では、そのコントラクトは実際にはコンパイラーによって強制されます。Pythonでは、EAFPまたはタイプイントロスペクションを使用して、不明なオブジェクトがこの予想される契約を満たしていることを確認できます。
しかし、契約にはより高いレベルの意味論的約束もあります。
たとえば、__str__()
メソッドがある場合、オブジェクトの文字列表現を返すことが期待されています。それは可能性があり、オブジェクトのすべての内容を削除し、トランザクションをコミットし、プリンタから空白のページを吐く...しかし、Pythonのマニュアルに記載されたそれは何をすべきかの共通の理解があります。
これは、セマンティックコントラクトがマニュアルに記述されている特殊なケースです。print()
メソッドは何をすべきですか?オブジェクトをプリンターに書き込むか、画面に線を書き込むか、それとも他の何かを書き込む必要がありますか?状況によって異なります。ここで完全な契約を理解するには、コメントを読む必要があります。print()
メソッドが存在することを確認するだけのクライアントコードは、コントラクトの一部を確認しました。つまり、メソッド呼び出しを行うことができますが、呼び出しのより高いレベルのセマンティクスについては合意がありません。
抽象基本クラス(ABC)の定義は、クラスの実装者と呼び出し元の間のコントラクトを作成する方法です。これは単なるメソッド名のリストではなく、それらのメソッドが何をすべきかについての共通の理解です。このABCから継承する場合、print()
メソッドのセマンティクスを含む、コメントで説明されているすべてのルールに従うことを約束します。
Pythonのダックタイピングには、静的タイピングよりも柔軟性に多くの利点がありますが、すべての問題を解決できるわけではありません。ABCは、自由形式のPythonと静的型付け言語の束縛および規律との間の中間ソリューションを提供します。
collections.Container
は退化したケースで、だけが含まれ\_\_contains\_\_
、事前定義された規則を意味するだけです。ABCを使用しても、それだけではそれほど価値はありません。Set
継承できるようにするために追加されたのではないかと思います。あなたがにたどり着くまでにSet
、突然ABCに属することはかなりの意味論を持っています。アイテムをコレクションに2回含めることはできません。メソッドの存在によっては検出できません。
Set
はより良い例だと思いますprint()
。意味があいまいで、名前だけではわからないメソッド名を見つけようとしていたので、その名前とPythonのマニュアルだけでは正しいことを実行できるかどうか確信が持てませんでした。
Set
代わりに例として答えを書き直す可能性はありprint
ますか?Set
@Oddthinking、とても理にかなっています。
Oddthinkingの答え@間違っていないですが、私はそれはミスだと思う本物の、実用的な Pythonはダックタイピングの世界ではいろはを持っている理由を。
抽象メソッドはすばらしいですが、私の意見では、ダックタイピングでまだカバーされていないユースケースを実際には満たしていません。抽象基本クラスの本当の力は、およびの動作をカスタマイズできる方法に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__
属性を持つクラスは、直接サブクラス化していなくても、コンテナのサブクラスと見なされることを示しています。だから私はこれを書くことができます:
class ContainAllTheThings(object):
def __contains__(self, item):
return True
>>> issubclass(ContainAllTheThings, collections.Container)
True
>>> isinstance(ContainAllTheThings(), collections.Container)
True
つまり、適切なインターフェースを実装すれば、あなたはサブクラスです!ABCは、ダックタイピングの精神に忠実でありながら、Pythonでインターフェースを定義する正式な方法を提供します。さらに、これは開閉原理を尊重する方法で機能します。
Pythonのオブジェクトモデルは、表面的には、より「伝統的な」OOシステム(つまり、Java *)のオブジェクトモデルに似ています。クラス、オブジェクト、メソッドがありますが、表面をスクラッチすると、はるかに豊かでより柔軟。同様に、Pythonの抽象基本クラスの概念はJava開発者には認識できるかもしれませんが、実際には非常に異なる目的を意図しています。
単一のアイテムまたはアイテムのコレクションに作用する多態性関数を作成しているときがあり、同等のブロックisinstance(x, collections.Iterable)
よりもはるかに読みやすくなっています。(Pythonを知らなかった場合、この3つのどれがコードの意図を最も明確にするでしょうか?)hasattr(x, '__iter__')
try...except
とは言っても、自分でABCを書く必要はほとんどなく、リファクタリングを通じてその必要性を発見するのが普通です。ポリモーフィック関数が多数の属性チェックを行っている、または多数の関数が同じ属性チェックを行っているのを見た場合、その匂いは抽出されるのを待っているABCの存在を示唆しています。
* Javaが「伝統的な」オブジェクト指向システムであるかどうかについての議論に入る必要はありません...
補遺:抽象基本クラスはisinstance
and の動作をオーバーライドできますが、仮想サブクラスのMROにはissubclass
入りません。これはクライアントにとって潜在的な落とし穴です。で定義されたメソッドを持つすべてのオブジェクトとは限りません。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
残念ながら、これらの「それをしないでください」というトラップ(Pythonには比較的少ない)があります。A __subclasshook__
と非抽象メソッドの両方でABCを定義することは避けてください。さらに、__subclasshook__
ABCが定義する一連の抽象メソッドと一貫性のある定義を作成する必要があります。
isinstance(x, collections.Iterable)
がわかりやすく、Pythonを知っています。
C
サブクラスが削除(または修理を超えて台無し)abc_method()
から継承されたがMyABC
。主な違いは、サブクラスではなく継承コントラクトを台無しにしているのがスーパークラスであることです。
Container.register(ContainAllTheThings)
与えられた例が機能するために行う必要はありませんか?
__subclasshook__
は、「この述部を満たすクラスは、ABCに登録されているかどうかに関係なく、また直接のサブクラスかどうかに関係なく、目的isinstance
とissubclass
チェックのためにサブクラスと見なされます」です。答えで述べたように、適切なインターフェースを実装すれば、あなたはサブクラスです!
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に感謝
これにより、プロトコル内のすべてのメソッドの存在を確認する必要なく、またはサポートされていないために「敵」の領域の深いところで例外をトリガーすることなく、オブジェクトが特定のプロトコルをサポートするかどうかを判断できます。
抽象メソッドは、親クラスで呼び出すメソッドが子クラスに表示される必要があることを確認します。以下は、抽象を呼び出して使用する通常の方法です。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()が呼び出されました」
__contains__
するクラスと継承するクラスの違いは何collections.Container
ですか?あなたの例では、Pythonでは常にの共通の理解がありました__str__
。実装__str__
は、ABCから継承して実装するのと同じ約束をします__str__
。どちらの場合も、契約を解除できます。静的型付けにあるような証明可能なセマンティクスはありません。