LoginSignup
8
5

More than 5 years have passed since last update.

type hinting で複数の Protocol を実装したクラスの型を表現する

Last updated at Posted at 2019-03-28

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 ベースなプログラミングをする上では非常に重要な機能なのでぜひ実装してもらいたい気持ちです。

8
5
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5