abc – 抽象基底クラス¶
目的: | コードをチェックする API のために抽象基底クラスの使用、定義する |
---|---|
利用できるバージョン: | 2.6 |
なぜ抽象基底クラスを使用するのか?¶
抽象基底クラス(ABC)は特定メソッドをチェックする個別の hasattr() よりも厳密なチェックのためのインタフェースです。抽象基底クラスを定義することでサブクラスセットのための共通 API を定義することができます。この機能はアプリケーションに対するプラグインのような、サードパーティが実装を提供する状況でかなり便利です。さらに、同時に頭の中で全クラスを把握することが難しい、もしくは不可能な、大規模なチーム又はコードベースで開発することもできます。
どのように ABC を用いて開発するか¶
abc は抽象的な基底クラスのメソッドをマークしてから、抽象基底クラスの実装として具象クラスを登録します。コードが特定 API を要求するなら、抽象クラスに対するオブジェクトをチェックするために issubclass() か isinstance() を使用することができます。
データの保存や読み込みのためのプラグインセットの API を表すために抽象基底クラスを定義してみましょう。
import abc
class PluginBase(object):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def load(self, input):
"""Retrieve data from the input source and return an object."""
return
@abc.abstractmethod
def save(self, output, data):
"""Save the data object to the output."""
return
具象クラスを登録する¶
そのクラスを ABC で登録するか、ABC から直接サブクラス化するかといった抽象的なモノを実装する具象クラスを指し示す方法が2つあります。
import abc
from abc_base import PluginBase
class RegisteredImplementation(object):
def load(self, input):
return input.read()
def save(self, output, data):
return output.write(data)
PluginBase.register(RegisteredImplementation)
if __name__ == '__main__':
print 'Subclass:', issubclass(RegisteredImplementation, PluginBase)
print 'Instance:', isinstance(RegisteredImplementation(), PluginBase)
この例の RegisteredImplementation は PluginBase から派生しませんが PluginBase API の実装として登録されます。
$ python abc_register.py
Subclass: True
Instance: True
サブクラス化による実装¶
その基底クラスから直接サブクラス化することで明示的にその具象クラスを登録する必要はありません。
import abc
from abc_base import PluginBase
class SubclassImplementation(PluginBase):
def load(self, input):
return input.read()
def save(self, output, data):
return output.write(data)
if __name__ == '__main__':
print 'Subclass:', issubclass(SubclassImplementation, PluginBase)
print 'Instance:', isinstance(SubclassImplementation(), PluginBase)
このケースでは、通常の Python のクラス管理が抽象クラス PluginBase を実装する SubclassImplementation を認識します。
$ python abc_subclass.py
Subclass: True
Instance: True
直接サブクラス化することの副作用は、抽象クラスから派生したクラスリストの基底クラスを調べることでプラグインの全ての実装を見つけられることです(これは ABC の機能ではなく、全てのクラスがこのように動作します)。
import abc
from abc_base import PluginBase
import abc_subclass
import abc_register
for sc in PluginBase.__subclasses__():
print sc.__name__
abc_register がインポートされたとしても、実際にはその基底クラスから派生していないので RegisteredImplementation がサブクラスのリストに存在しないことに気付いてください。
$ python abc_find_subclasses.py
SubclassImplementation
Dr. André Roberge は動的にディレクトリ内の全モジュールをインポートしてから実装クラスを見つけるためにサブクラスリストを探することで、プラグインを発見するためにこの機能を使用することを 説明しました 。
不完全な実装¶
抽象基底クラスから直接サブクラス化することの他の利点は API の抽象的な部分を完全に実装しない限りサブクラスはインスタンス化されないということです。これは中途半端な実装で実行時に予測されないエラーを発生させないようにします。
import abc
from abc_base import PluginBase
class IncompleteImplementation(PluginBase):
def save(self, output, data):
return output.write(data)
PluginBase.register(IncompleteImplementation)
if __name__ == '__main__':
print 'Subclass:', issubclass(IncompleteImplementation, PluginBase)
print 'Instance:', isinstance(IncompleteImplementation(), PluginBase)
$ python abc_incomplete.py
Subclass: True
Instance:
Traceback (most recent call last):
File "abc_incomplete.py", line 22, in <module>
print 'Instance:', isinstance(IncompleteImplementation(), PluginBase)
TypeError: Can't instantiate abstract class IncompleteImplementation with abstract methods load
ABC の具象メソッド¶
具象クラスは抽象メソッドの実装を提供しなければならないですが、抽象基底クラスが super() を通して実行される実装を提供することもできます。これは基底クラスに抽象メソッドの実装を配置することで共通ロジックを再利用させますが、(潜在的に)カスタムロジックでオーバーライドされたメソッドを提供することをサブクラスに強制します。
import abc
from cStringIO import StringIO
class ABCWithConcreteImplementation(object):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def retrieve_values(self, input):
print 'base class reading data'
return input.read()
class ConcreteOverride(ABCWithConcreteImplementation):
def retrieve_values(self, input):
base_data = super(ConcreteOverride, self).retrieve_values(input)
print 'subclass sorting data'
response = sorted(base_data.splitlines())
return response
input = StringIO("""line one
line two
line three
""")
reader = ConcreteOverride()
print reader.retrieve_values(input)
print
ABCWithConcreteImplementation は抽象基底クラスなので、そのクラスを直接インスタンス化することはできません。サブクラスは retrieve_values() をオーバーライドして 提供しなければなりません 。そして、このケースでは具象クラスが入力データを返す前にメッセージを表示します。
$ python abc_concrete_method.py
base class reading data
subclass sorting data
['line one', 'line three', 'line two']
抽象プロパティ¶
API 仕様がメソッドに加えて属性を含むなら @abstractproperty で、そのようなメソッドを定義することで具象クラスに属性を要求することができます。
import abc
class Base(object):
__metaclass__ = abc.ABCMeta
@abc.abstractproperty
def value(self):
return 'Should never get here'
class Implementation(Base):
@property
def value(self):
return 'concrete property'
try:
b = Base()
print 'Base.value:', b.value
except Exception, err:
print 'ERROR:', str(err)
i = Implementation()
print 'Implementation.value:', i.value
このサンプルの Base クラスは抽象プロパティのゲッタメソッドしか持たないのでインスタンス化できません。
$ python abc_abstractproperty.py
ERROR: Can't instantiate abstract class Base with abstract methods value
Implementation.value: concrete property
抽象的な read/write プロパティを定義することもできます。
import abc
class Base(object):
__metaclass__ = abc.ABCMeta
def value_getter(self):
return 'Should never see this'
def value_setter(self, newvalue):
return
value = abc.abstractproperty(value_getter, value_setter)
class PartialImplementation(Base):
@abc.abstractproperty
def value(self):
return 'Read-only'
class Implementation(Base):
_value = 'Default value'
def value_getter(self):
return self._value
def value_setter(self, newvalue):
self._value = newvalue
value = property(value_getter, value_setter)
try:
b = Base()
print 'Base.value:', b.value
except Exception, err:
print 'ERROR:', str(err)
try:
p = PartialImplementation()
print 'PartialImplementation.value:', p.value
except Exception, err:
print 'ERROR:', str(err)
i = Implementation()
print 'Implementation.value:', i.value
i.value = 'New value'
print 'Changed value:', i.value
具象プロパティは抽象プロパティと同じ方法で定義されなければならないことに注意してください。 PartialImplementation の read/write プロパティを ‘Read-only’ でオーバーライドしようとしても動作しません。
$ python abc_abstractproperty_rw.py
ERROR: Can't instantiate abstract class Base with abstract methods value
ERROR: Can't instantiate abstract class PartialImplementation with abstract methods value
Implementation.value: Default value
Changed value: New value
抽象的な read/write プロパティを扱うデコレータ構文を使用するために value を get/set するメソッドは同じ名前にすべきです。
import abc
class Base(object):
__metaclass__ = abc.ABCMeta
@abc.abstractproperty
def value(self):
return 'Should never see this'
@value.setter
def value(self, newvalue):
return
class Implementation(Base):
_value = 'Default value'
@property
def value(self):
return self._value
@value.setter
def value(self, newvalue):
self._value = newvalue
i = Implementation()
print 'Implementation.value:', i.value
i.value = 'New value'
print 'Changed value:', i.value
Base と Implementation クラスの2つのメソッドは引数は違いますが value() という名前で定義していることに注意してください。
$ python abc_abstractproperty_rw_deco.py
Implementation.value: Default value
Changed value: New value
コレクション型¶
collections モジュールはコンテナ(とコンテナにできる)型に関連する複数の抽象基底クラスを定義します。
- Container
- Sized
イテレータとシーケンスクラス:
- Iterable
- Iterator
- Sequence
- MutableSequence
ユニークな値:
- Hashable
- Set
- MutableSet
マッピング:
- Mapping
- MutableMapping
- MappingView
- KeysView
- ItemsView
- ValuesView
その他:
- Callable
抽象基底クラスの現実世界の詳細なサンプルとして提供することに加えて Python のビルトイン型は collections をインポートするとき、これらのクラスに対して自動的に登録されます。これは必要とする API をこれらのクラスがサポートすることを保証して、コードのパラメータをチェックするために isinstance() を安全に使用できることを意味します。さらに、これらのクラスの多くは内部の具象的な実装を提供してオーバーライドされた数個のメソッドのみを必要とするので、その基底クラスは独自のコレクション型を定義するために使用されます。詳細はコレクションモジュールの標準ライブラリドキュメントを参照してください。
See also
- abc
- 本モジュールの標準ライブラリドキュメント
- PEP 3119
- 抽象基底クラスの紹介
- collections
- コレクション型のために抽象基底クラスを含むコレクションモジュール
- collections
- コレクションモジュールの標準ライブラリドキュメント
- PEP 3141
- 数値のデータ型の階層構造
- Wikipedia: Strategy Pattern
- ストラテジパターンの例と説明
- Plugins and monkeypatching
- Dr. André Roberge による PyCon 2009 のプレゼンテーション