9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

あなた(わたし)のための型ヒント - 型変数TypeVar入門

Posted at

この記事は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の利用を検討いただけると幸いです。

  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:

9
4
0

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
9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?