Python でインタフェースの機能を使う方法をご紹介します。
・・・といっても、 Python 3.6 の時点で Python には言語機能としてのインタフェースは存在しません。具体的にいうと、「継承先に特定のインタフェースの実装を強制できるような仕組み」が Python にはありません。
しかし、標準ライブラリの abc
を使うと次の 2 つのことが実現できます。
- a. 複数のクラスを抽象クラスでまとめる
- b. 継承先クラスのインスタンス生成時にメソッドの存在チェックをかける
このことをもって、「 Python でも他の言語でいうところの『インタフェース』の機能をある程度は利用できる 」と考えてもよいのではないかな、と個人的には思います。
今回はこの Python におけるインタフェース的な機能を abc
で実現する方法をご紹介します。上の a b を順に見ていきましょう。
a. 複数のクラスを抽象クラスでまとめる
最初に、複数のクラスを抽象クラスでまとめる方法を見てみます。
複数の異なるクラスを抽象クラスでまとめることで、それらのクラスのオブジェクトに対して isinstance()
が True を返すようなチェック機構を作ることができます。
具体的なコードを見てみましょう。
# coding: utf-8
'''デジタルコンテンツインタフェースを作ってテストする
'''
from abc import ABC
class Movie:
'''映画
'''
pass
class Book:
'''書籍
'''
pass
class DigitalContentInterface(ABC):
'''デジタルコンテンツのインタフェース
'''
pass
# 映画と書籍を DigitalContentInterface の仮想サブクラスとして登録する
DigitalContentInterface.register(Movie)
DigitalContentInterface.register(Book)
def is_digital_content(object):
'''オブジェクトがデジタルコンテンツかどうかをチェック
'''
return isinstance(object, DigitalContentInterface)
# Movie のインスタンスは DigitalContentInterface のインスタンスと認識される
m1 = Movie()
print(is_digital_content(m1)) # => True
# Book のインスタンスも DigitalContentInterface のインスタンスと認識される
b1 = Book()
print(is_digital_content(b1)) # => True
# 登録されていないクラスのインスタンスでは当然 False が返る
d1 = {}
print(is_digital_content(d1)) # => False
ここでは abc.ABC
というクラスを継承する形で DigitalContentInterface
というインタフェース用のクラスを作成しました。
続いて、クラスメソッド register()
を使って、 Movie
と Book
という 2 つのクラスを DigitalContentInterface
の仮想サブクラスとして登録しています。
その後に isinstance()
でチェックをかけると、 Movie
と Book
のインスタンスは DigitalContentInterface
のインスタンスと認識されることが確認できます。
おもしろいですね。
register()
メソッドが便利なのは、組み込みのクラスを含む定義済みのクラスも「独自に作ったインタフェース」の仮想サブクラスとして登録できるところです。
# リストは本来(当然) DigitalContentInterface のインスタンスではない
l1 = []
print(is_digital_content(l1)) # => False
# リストクラスを DigitalContentInterface の仮想サブクラスとして登録すると・・・
DigitalContentInterface.register(list)
# リストのインスタンスも DigitalContentInterface のインスタンスと認識されるようになる
print(is_digital_content(l1)) # => True
ちなみに、この、特定のクラスを仮想サブクラスとして登録する register()
メソッドを使った方法とは別に、クラスメソッド __subclasshook__()
を使った方法も用意されています。
公式のサンプルを少し縮めたサンプルを見てみましょう。
class MyIterable(ABC):
@classmethod
def __subclasshook__(cls, C):
if cls is MyIterable:
if any("__iter__" in B.__dict__ for B in C.__mro__):
return True
return NotImplemented
ここでは、 __subclasshook__()
メソッドを実装することで「 __iter__()
メソッドを実装しているクラスは MyIterable
のサブクラスとみなす」というロジックが実現されています。 __subclasshook__()
を使えば、このようにより柔軟なロジックで仮想サブクラスを設定することができます(例えば、インタフェースの定義時には具体的な仮想サブクラスがわからない状態でも仮想サブクラスを登録することができます)。
ちなみに、この abc.ABC
はメタクラス abc.ABCMeta
をよりシンプルに扱えるクラスとして Python 3.4 で導入されました。
a については以上です。続いて b の方を説明していきます。
b. 継承先クラスのインスタンス生成時にメソッドの存在チェックをかける
こちらは、よりインタフェースっぽい感じのふるまいが実現できる機能です。
こちらも先にサンプルコードを見てみましょう。
# coding: utf-8
'''デジタルコンテンツインタフェースでメソッドの実装の強制機能をテストする
'''
from abc import ABC
from abc import abstractmethod
class DigitalContentInterface(ABC):
'''デジタルコンテンツのインタフェース
'''
def __init__(self, title):
self.title = title
@abstractmethod
def format_title(self):
pass
class Movie(DigitalContentInterface):
def format_title(self):
return 'Movie: {}'.format(self.title)
class Book(DigitalContentInterface):
pass
# Movie のインスタンスは問題なく生成できる
m1 = Movie('From Dusk Till Dawn')
print(m1.format_title())
# => Movie: From Dusk Till Dawn
print(isinstance(m1, DigitalContentInterface))
# => True
# Book のインスタンスは
# Book が format_title() メソッドを定義していないので作成できない
b1 = Book('The Martian')
# TypeError: Can't instantiate abstract class Book with abstract methods format_title
以下、コード内の処理の流れを説明します。
まず最初に、 a と同じような形で abc.ABC
クラスを継承した DigitalContentInterface
というクラスを定義しています。この中で abc.abstractmethod
というデコレータ用の関数を使って format_title()
というメソッドをアブストラクトメソッドとして登録しています。
つづいて、 a の register()
を使った形ではなく、通常の継承のシンタックスを使って DigitalContentInterface
を継承したクラス Movie
と Book
を定義しています。
実際にインスタンスを生成しようとすると、 Movie
の方は問題なく生成できますが、 Book
の方はアブストラクトメソッド format_title()
をきちんと実装していないということで例外が上がって生成できません。
・・・というように、「インスタンス生成時にメソッドの存在チェックをかける」ことができていることが確認できます。
こちらもおもしろいですね。うまく活用できると便利そうです。
ただし、 b の方は使う際に押さえておくべき注意点がいくつかあります。
register()
を使った方法で登録した仮想サブクラスに対してはチェックが走らない@abstractmethod
で登録されたメソッド自身が `pass` 以外の実装を持つことができて、サブクラスからsuper()
で呼び出すことができる
ここで取り上げたのはインスタンスメソッドを使ったパターンだけでしたが、クラスメソッド、スタティックメソッド、プロパティに対しても同様のチェックを行うこともできます。その場合はデコレータの記述順などにも厳密なルールがあるため、興味のある方は利用の前に公式のドキュメントなどでしっかり確かめてから使うようにするとよいかと思います。
以上です。
おもしろいですねー。
参考