Python 3.0 Hacks

第2回abcモジュールによる抽象基底クラスの作成

抽象クラスとインターフェース

C++には、抽象クラスという仕組みがあります。

抽象クラスとは、インターフェースのみを定義した純粋仮想関数というメンバ関数をもつクラスです。抽象クラスはそのままでは定義が完全ではないため、継承して純粋仮想関数をすべてオーバライドしなければインスタンスを作れません。

このような抽象クラスを用いることは、クラスのインターフェースを定義し、継承時にオーバライドしてほしいメンバ関数を明示する、という意味があります。

Python においては、そのようなインターフェースのみを定義するという機能が存在しません。そのため、クラスを継承する際にオーバライドするべきメソッドを明示し、オーバライドされていなければインスタンスを作れなくする、というような挙動をさせることは通常できません。

似たような挙動をさせるには、 未実装であることの印としてNotImplementedオブジェクトを返すことがありますが、この場合は実際に未実装なのか、抽象クラスなのかの判別ができません。

abcモジュールによる抽象クラス

そのような状況だったPythonですが、Python 3.0でabcというモジュールが新しく追加されました。このモジュールは2.6にも同時に追加されています。abcという名前だけを見ると、なんとなくネタっぽい感じですが、これはAbstract Base Classes」の略です。直訳すると「抽象基底クラス」といったところでしょうか。

このモジュールは、その名の通り Python において抽象クラスをサポートするためのモジュールです。

抽象クラスの定義

まずはこのモジュールを使用して、抽象クラスを定義してみます。

リスト1
#-*- coding:utf-8 -*-
import abc


class BaseShape(object, metaclass=abc.ABCMeta):
    ''' 形を定義するための基底クラス '''

    @abc.abstractmethod
    def getSize(self):
        ''' 面積を計算して返す抽象メソッド '''
        pass

リスト1では、 BaseShapeというクラスを定義しています。これは、図形を表現するための基底クラスとして作成しました。

通常のクラス定義と違うのは、BaseShapeのメタクラスにabcモジュールのABCMetaを渡している部分と、getSizeメソッドのデコレータにabstructmethodを使用している部分です。

abstructmethodというデコレータは、修飾対象のメソッドに抽象メソッドであることを示す__isabstractmethod__という属性を追加します。

同様のデコレータにabstractpropertyというものがあります。これは、メソッドではなくプロパティに対してabstractmethodと同様の処理を行います。

次に、BaseShapeのメタクラスとして指定しているABCMetaクラスです。メタクラスそのものについて詳しく書くと、それだけで結構な量になってしまうので、ここではあまり詳しく書きません。簡単に言うと、クラスを生成する際の処理をカスタマイズするための仕組みがメタクラスです。クラスが定義される直前や直後に、クラスに対して処理を行えます。この仕組みを利用して、abstractmethod/abstractpropertyで付けた抽象メソッドであるという印をチェックし、抽象クラスであるかどうかを判別しています(コラム参照⁠⁠。

それでは、このBaseShapeクラスのインスタンスを作成してみます。

リスト2
>>> BaseShape()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class BaseShape with abstract methods getSize

すると、リスト2のように「抽象クラスはインスタンスを作成できない」という内容のエラーが出力され、実装されていない抽象メソッドが列挙されます。

継承とオーバライド

ここで、 BaseShape クラスを継承して、新しいクラスを作成してみます。

リスト3
import math
    
class Circle(BaseShape):
    ''' 円を定義 '''

    def __init__(self, radius):

        self.radius = radius


    def getSize(self):
        ''' 面積を計算 '''

        return self.radius **  2 * math.pi

今回は、BaseShapeクラスを継承し、円を表すCircleクラスを作成しました。オーバライドしたgetSizeでは、コンストラクタで受け取った半径を元に面積を計算して返しています。

このCircleクラスを実際に使用してみます。

リスト4
>>> b = Circle(10)
>>> print(b.getSize())
314.159265359

このようにインスタンスが作成でき、getSizeで面積を計算して返すようになりました。このabcモジュールを使用すると、C++のように抽象クラスを用いたインターフェースの定義を利用することができます。

おすすめ記事

記事・ニュース一覧