なぜPythonで抽象的なベースクラスを使用するのですか?
-
01-10-2019 - |
解決
短縮版
ABCは、クライアントと実装されたクラスの間でより高いレベルのセマンティック契約を提供します。
ロングバージョン
クラスとその発信者の間に契約があります。クラスは、特定のことを行い、特定のプロパティを持つことを約束します。
契約には異なるレベルがあります。
非常に低いレベルでは、契約にはメソッドの名前またはパラメーターの数が含まれる場合があります。
静的な型の言語では、その契約は実際にコンパイラによって実施されます。 Pythonでは、EAFPまたは内省を使用して、未知のオブジェクトがこの予想される契約を満たしていることを確認できます。
しかし、契約には高レベルの意味的な約束もあります。
たとえば、aがあります __str__()
方法、オブジェクトの文字列表現を返すことが期待されます。それ たぶん......だろう オブジェクトのすべてのコンテンツを削除し、トランザクションをコミットし、プリンターから空白ページを吐き出します...しかし、Pythonマニュアルで説明されていることについては、それが何をすべきかについてよく理解しています。
これは特別なケースであり、セマンティック契約についてはマニュアルに記載されています。何がすべきか print()
方法は?オブジェクトをプリンターに書いたり、画面にラインに書いたりする必要がありますか?それは依存します - ここで完全な契約を理解するためにコメントを読む必要があります。単にそれをチェックするクライアントコードの一部 print()
メソッドが存在していることが契約の一部を確認しています - メソッド呼び出しを行うことができるが、コールのより高いレベルのセマンティクスに合意があるということではありません。
抽象的な基本クラス(ABC)を定義することは、クラスの実装者と発信者の間で契約を作成する方法です。それは単なるメソッド名のリストではなく、それらのメソッドが何をすべきかを共有することです。このABCから継承する場合、コメントに記載されているすべてのルールに従うことを約束しています。 print()
方法。
Pythonのアヒルタイピングには、静的タイピングよりも柔軟性に多くの利点がありますが、すべての問題は解決しません。 ABCは、Pythonのフリーフォームと静的なタイプの言語のボンデージアンドディスカップラインとの間の中間ソリューションを提供します。
他のヒント
@Oddthinkingの答えは間違っていませんが、私はそれが見逃していると思います 本物, 実用的 Reason Pythonには、アヒルタイピングの世界にABCがあります。
抽象的な方法はきちんとしていますが、私の意見では、彼らは実際にはアヒルのタイピングでカバーされていないユースケースを実際に埋めていません。抽象的なベースクラスの本当の力があります 彼らがあなたがの動作をカスタマイズすることを可能にする方法 isinstance
と issubclass
. (__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システムであるかどうかについて議論することなく...
補遺: :抽象的なベースクラスがの動作をオーバーライドできる場合でも 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
残念ながら、これらの「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()は呼び出されます