この記事はJSL(日本システム技研) Advent Calendar 2021のn日目の記事です!
あなた(わたし)のための型ヒント - 型変数TypeVar入門
型に憧れています。
中でもJavascriptで T と書くあれ。そうジェネリクス。これがよくわかりません。
Pythonでも標準ライブラリのTypingモジュールに、TypeVarというものがあります。
ドキュメントを読んでまとめたのが本記事になります。型弱者なあなた(わたし)の理解の助けになれば幸いです。
typing.TypeVar --- 型ヒントのサポート — Python 3 ドキュメント
注意
Pythonは動的型付け言語です。どんなに型が間違っていても、 実行 はできてしまいます。
>>> age: int = "こんにちは!" # int型の変数ageに文字列を代入
>>> print(age)
こんにちは!
正しく型が利用できているか確認するため、必ずmypy1などの静的型チェックツールと合わせて利用しましょう。
実行できてしまうのに、なぜPythonで型をつけるのでしょうか?
それはコードの理解を助けるためです。
あの関数やあのメソッドの引数には何が指定できるのか、戻り値は何が返ってくるのか、型ヒント(とできればVS CodeやPyCharm)があると、それらを教えてくれます。
「あの人の書いた共通関数、どうやって使うんだっけ...」
「1週間前に書いた自分のコードを全く理解できない...」
みなさんも経験があるのではないでしょうか?
あなた(明日のわたし)のための型、それが型ヒントになります。
Pythonで型ヒントが導入される経緯などがDropbox社のブログにまとめらています。ぜひご一読ください。
Python の型チェックが 400 万行に到達するまで - Dropbox からのお知らせ
TypeVarとは
TypeVarは型変数と呼ばれる型を定義できます。
型変数で型を付けた場合、同じ型であるという意味になります(デフォルトで 不変 という性質を持ちます 2 )。
具体的な型(strやintなど)は 利用するときに 指定するイメージです。
型変数は関数の引数や戻り値、またクラス(typingモジュールのGenericなどと合わせて定義)で利用し、 主に汎用的な機能やライブラリを作る場合に効果を発揮します。
本記事では、TypeVarのイメージを掴んでもらうのが目的のため、Genericの説明はしません。詳しくは公式ドキュメントを参照してみてください。
ジェネリクス --- 型ヒントのサポート — Python 3 ドキュメント
TypeVarで型を定義
定義は次の例のように、第一引数に代入する変数名と同じ名前を渡す必要があります。
from typing import TypeVar
# 正しい定義
T = TypeVar("T")
CHECK = TypeVar("CHECK")
SAME = TypeVar("SAME")
# 間違った定義
ONE = TypeVar("TWO")
TypeVarを関数の型付けで利用する
2つの引数のうち、いずれかを1/2の確率で返すlottery()関数を定義してみましょう。
関数の引数、戻り値にTypeVarで定義した型変数「T」を指定します。
import random
from typing import TypeVar
T = TypeVar("T") # TypeVarを変数Tに定義
def lottery(ans_a: T, ans_b: T) -> T: # 引数、戻り値の型にTを指定
"""1/2の確率でいずれかの引数を返す"""
if random.random() <= 0.5:
return ans_a
else:
return ans_b
TypeVarの不変という性質によりlottery()関数を実行した時に、intを渡した場合にはintが、strを渡した場合にはstrが返ってくるという定義になります。
正しい使い方
上記のlottery()
関数を利用してみましょう。以下であればmypyに怒られません。
# 数値を引数として渡し、戻り値として数値を受け取る
a_num: int = 1
b_num: int = 2
ans_num: int = lottery(a_num, b_num) # 戻り値は数値
# 文字列を引数として渡し、文字列を受け取る
a_str: str = "win"
b_str: str = "loose"
ans_str: str = lottery(a_str, b_str) # 戻り値は文字列
# リストを引数として渡し、リストを受け取る
a_list: list = ["みかん", "いちご", "バナナ"]
b_list: list = ["ヘーゼルナッツ", "クルミ", "アーモンド"]
ans_list: list = lottery(a_list, b_list) # 戻り値はリスト
間違った使い方
以下の例が間違った使い方になります。mypyに怒られてしまいます。
# 2つの引数に異なる型の値を渡す
ng_num: int = 1
ng_str: str = "2"
ng_ans = lottery(ng_num, ng_str)
もしTypeVarを利用せずに前述のlottery()
関数を定義する場合、以下の3つがそれぞれ必要になります。
- 数値を受け取り数値を返す関数
- 文字列を受け取り文字列を返す関数
- リストを受け取りリストを返す関数
まとめ
処理は同じで引数や戻り値の型のみが異なる場合にTypeVarは効果を発揮します。
Pythonで型を書いていて共通処理を作らなければならない場合、TypeVarの利用を検討いただけると幸いです。
-
mypyはOSSの静的型チェックツールです。Pythonの父、Guido van Rossum氏も開発に携わっている準公式とも言えるツールです。 python/mypy: Optional static typing for Python ↩
-
他にも共変
covariant=True
または反変contravariant=True
があります。詳細はtyping --- 型ヒントのサポート — Python 3 ドキュメントや、PEP 484 -- Type Hints | Python.orgを参照してください ↩