Python学習チャンネル by PyQ

Pythonのオンライン学習プラットフォームPyQのオフィシャルブログです

【Pythonお悩み解決】Pythonの型ヒントで使われるプロトコルとは

PyQチームのtsutomuです。 今回は、Pythonの型ヒントで使われるプロトコル(Protocol)について紹介します。

docs.python.org

プロトコルとは型ヒントをサポートする機能

Pythonは型を書いても書かなくてもよい言語です。
型を書くのは手間がかかりますが、品質を上げたり保守しやすいというメリットがあります。
Pythonで記述する型は、基本的に実行時に影響しません。このことから型ヒントとよばれています。

今回紹介するプロトコルは、型ヒントをサポートする機能です。

プロトコルを使った具体例

具体例で説明します。
次の関数print_pop()は、引数itemのメソッドpop()を実行して表示します。

def print_pop(item):
    # itemは、popメソッドを持つことを前提としている
    print(item.pop())

print_pop([1])  # リストを渡した場合
print_pop({1})  # 集合を渡した場合

このprint_pop()の引数の型はどのように指定すれば良いでしょうか?
pop()というメソッドはリストや集合で使えますが、他にも使えるデータ型があるかもしれません。

ここでやりたいことは「pop()というメソッドを持っている型」を記述することです。

ひとつの考え方として、「pop()メソッドを持っているインターフェースを定義する」という方法があります。
しかし、新たにインタフェースを定義した場合、自作のクラスはコードを修正すれば対応できますが、組み込みの型であるリストや集合に手を加えることはできないので直すことはできません。
つまり、そのままでは使えません。

このようなときに、プロトコルが使えます。具体的に書いてみましょう。

from typing import Any, Protocol

class Poppable(Protocol):
    def pop(self) -> Any:
        pass  # 実装不要

pop()というメソッドを持っている型」としてPoppableというプロトコルを作成しました。
新しいプロトコルは、Protocolから派生させることで作成できます。
このときメソッドの実装は不要です。

プロトコルは、通常の型と同様に型ヒントで指定できます。先ほどのprint_pop()関数の引数itemに Poppableを指定する場合、次のようになります。

def print_pop(item: Poppable) -> None:
    print(item.pop())

これで、itemに要求される特性をコードとして記述できました。

プロトコルが合わないコードの検出

実際にpop()を持たないタプルを引数にしてmypyで確認してみましょう。まず、下記の記述をファイルsample.pyに保存します。

from typing import Any, Protocol


class Poppable(Protocol):
    def pop(self) -> Any:
        pass


def print_pop(item: Poppable) -> None:
    print(item.pop())


print_pop([1])
print_pop({1})
print_pop((1,))  # タプルはpop()を持たない

mypy sample.pyを実行すると、下記のようにエラーになりました。

sample.py:15: error: Argument 1 to "print_pop" has incompatible type "Tuple[int]"; expected "Poppable"  [arg-type]
Found 1 error in 1 file (checked 1 source file)

まとめ

今回は型ヒントをサポートするプロトコルを紹介しました。
型ヒントは、バグの早期発見や(チームメンバーや将来の自分との)意思疎通に役立つものです。
是非、ご活用ください。

Copyright ©2017- BeProud Inc. All rights reserved.