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のように「抽象クラスはインスタンスを作成できない」という内容のエラーが出力され,実装されていない抽象メソッドが列挙されます。
abcモジュールのメタクラス処理
メタクラスでカスタマイズできるのは,クラスオブジェクトの生成部分のみです。そのため,クラスのインスタンスを作成する部分には手を加えることができず,インスタンス生成を止めることはできません。メタクラスの中で __init__ メソッドを置き換えるなどすれば,そのようなことも可能ですが,ABCMetaではそのようになっていないようです。
それでは,どのようにしてインスタンス作成を封じているかというと,クラスのインスタンスを作成する際に,インタプリタ側のCソースレベルで処理をしているようです。具体的には以下のような処理です。
- メタクラスの処理でabstractmethod/abstractpropertyで設定した印を見て,クラスオブジェクトの__abstractmethods__という属性に抽象メソッドの名前のfrozensetを保存
- インスタンスを作成する際に,クラスが__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++のように抽象クラスを用いたインターフェースの定義を利用することができます。
Python 3.0 Hacks
- 最終回 Python 3.0座談会(動画つき)
- 第11回 標準になったjsonモジュール
- 第10回 argument"s" clinic
- 第9回 Python3にもC拡張モジュールを─Python3.0でも使える拡張モジュール開発手法の確立
- 第8回 リスト処理と内包表記
- 第7回 関数アノテーションでスマートにプラスアルファの実現
- 第6回 Pythonicな文字列フォーマットforamat()メソッド
- 第5回 multiprocessingモジュールによるプロセス間通信
- 第4回 「俺様プチencoding」を実装して理解するPython3.0のioとcodec,encodingの機構
- 第3回 turtleモジュールで図と戯れる

