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)

この例の RegisteredImplementationPluginBase から派生しませんが 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

BaseImplementation クラスの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 のプレゼンテーション
Bookmark and Share