型に憧れています。中でもジェネリクス。

はじめに

型に憧れています。 中でもJavaScriptで T と書くあれ。そうジェネリクス。これがよくわかりません。

Pythonでも標準ライブラリのTypingモジュールに、TypeVarというものがあります。 ドキュメントを読んでまとめたのが本記事になります。型弱者なあなた(わたし)の理解の助けになれば幸いです。

typing.TypeVar --- 型ヒントのサポート — Python 3 ドキュメント

注意

Pythonは動的型付け言語です。どんなに型が間違っていても、 実行 はできてしまいます。

>>> age: int = "こんにちは!"  # int型の変数ageに文字列を代入
>>> print(age)
こんにちは!

実行できてしまうのに、なぜPythonで型をつけるのでしょうか?
それはコードの理解を助けるためです。あの関数やメソッドの引数には何が指定できるのか、戻り値は何が返ってくるのか、型ヒント(とできればVS CodeやPyCharm)があると、それらを教えてくれます。

「あの人の書いた共通関数、どうやって使うんだっけ…」
「1週間前に書いた自分のコードをまったく理解できない…」

みなさんも経験があるのではないでしょうか?昨日の自分は天才だったのかもしれません。
あなた(明日のわたし)のための型、それが型ヒントになります。

Pythonで型ヒントが導入される経緯などがDropbox社のブログにまとめらています。ぜひご一読ください。
Python の型チェックが 400 万行に到達するまで - Dropbox からのお知らせ

また、正しく型が利用できているか確認するため、Pythonで型を利用する場合は 必ず mypy1などの静的型チェックツールと合わせて利用しましょう。

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は効果を発揮します。


  1. mypyはOSSの静的型チェックツールです。Pythonの父、Guido van Rossum氏も開発に携わっている準公式とも言えるツーです。 python/mypy: Optional static typing for Python ↩︎

  2. 他にも共変 covariant=True または反変 contravariant=True があります。詳細はtyping --- 型ヒントのサポート — Python 3 ドキュメントや、PEP 484 -- Type Hints | Python.orgを参照してください :pray: ↩︎