Protocol / Interface
作っているプログラムが複雑になってくると、 Interface (python の世界では Protocol) を使って複数のクラスが同じインターフェースを持つことを保証したくなります。
特に mypy など type hinting の仕組みを使い始めるとそのようなものが欲しくなります。
このようなインターフェースは python では Protocol
というものを使うことで実装できます。
例えばあるクラスの情報を DB に書き出すために、 serialize
というメソッドを実装したクラス群があるとしましょう。この serialize というインターフェースを Serializable と呼ぶことにします。
from typing import Dict, Any
from typing_extensions import Protocol
class Serializable(Protocol):
def serialize(self) -> Dict[str, Any]:
...
class A:
def __init__(self) -> None:
self.data = None
def serialize(self) -> Dict[str, Any]:
return {'data': self.data}
def run(self) -> None:
self.data = 'run result'
def print_serialized(instance: Serializable) -> None:
print(instance.serialize())
a = A()
print_serialized(a)
print_serialized は何らか serialize メソッドを持ったクラスを受け取り、そのシリアライズ結果をプリントします。
いったいそのインスタンスがどんなクラスなのか、何をシリアライズするのかとかは知らなくてすみます。
複数の Protocol を実装したクラスを作りたい
さて、先程のクラス A には run というメソッドがありました。
このように、何らか run というメソッドをもっているというインターフェースを Runnable と呼ぶことにしましょう。
そして、 Runnable と Serializable の両方を持っているクラスのインスタンスを受け取ってなにか処理を行うことを考えます。
from typing import Dict, Any
from typing_extensions import Protocol
class Serializable(Protocol):
def serialize(self) -> Dict[str, Any]:
...
class Runnable(Protocol):
def run(self) -> None:
...
class A:
def __init__(self) -> None:
self.data = None
def serialize(self) -> Dict[str, Any]:
return {'data': self.data}
def run(self) -> None:
self.data = 'run result'
# ダサいぞ!!!
class RunAndSerializable(Serializable, Runnable, Protocol):
...
def print_result(instance: RunAndSerializable) -> None:
instance.run()
print(instance.serialize())
こういう書き方をすることになります。
print_result
ですが以下のように書きたいですね。。
def print_result(instance: Intersection[Serializable, Runnable]) -> None: # Serializable/Runnable 両方のインターフェースを実装しているクラス
instance.run()
print(instance.serialize())
このような Intersection 型ですが、2019/03現在検討はされているようですが、まだ実装には入っていないようです。
https://github.com/python/typing/issues/213
書かれていることを見ると
- Intersection は型一般に対応しようと思うと、 Generic などへの対応が非常に複雑になる
- Protocol のみに対応なら簡単だけど、それだと Union との対称性が無かったり、ニッチすぎる
- よって今はまだやる時期じゃない
個人的には Protocol ベースなプログラミングをする上では非常に重要な機能なのでぜひ実装してもらいたい気持ちです。