Python 3.0 Hacks

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

この記事を読むのに必要な時間:およそ 2 分

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

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のように「抽象クラスはインスタンスを作成できない」という内容のエラーが出力され,実装されていない抽象メソッドが列挙されます。

abcモジュールのメタクラス処理

メタクラスでカスタマイズできるのは,クラスオブジェクトの生成部分のみです。そのため,クラスのインスタンスを作成する部分には手を加えることができず,インスタンス生成を止めることはできません。メタクラスの中で __init__ メソッドを置き換えるなどすれば,そのようなことも可能ですが,ABCMetaではそのようになっていないようです。

それでは,どのようにしてインスタンス作成を封じているかというと,クラスのインスタンスを作成する際に,インタプリタ側のCソースレベルで処理をしているようです。具体的には以下のような処理です。

  1. メタクラスの処理でabstractmethod/abstractpropertyで設定した印を見て,クラスオブジェクトの__abstractmethods__という属性に抽象メソッドの名前のfrozensetを保存
  2. インスタンスを作成する際に,クラスが__abstractmethods__を持ち,何らかの名前が入っていれば,例外を投げる

そのため,abcモジュールを使用せずとも,

klass.__abstractmethods__ = {'aaa', 'bbb'}

というように,既存のクラスに__abstractmethods__ 属性を追加するコードを書くことで, クラスのインスタンスが作れなくなります。

継承とオーバライド

ここで, 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++のように抽象クラスを用いたインターフェースの定義を利用することができます。

著者プロフィール

保坂翔馬(ほさか しょうま)

CGプロダクションでプログラマとしてPloneで作成した社内情報共有ポータルの開発・管理,3Dモデリングソフトのツール・プラグイン開発,システム管理用補助ツール作成など幅広く仕事をしている。主たる業務はPloneポータルの開発。

仲間内ではプログラム言語オタクで通っており,好きな言語はPython, Haskell。

Twitter:@shomah4a

関連記事

コメント

コメントの記入