Python

Pythonで引数の型を利用した返り値の型定義を行う方法

どうやって型を付けますか?

float型もしくはint型のxとint型のnを受け取り、n個のxからなるlist型のオブジェクトを出力する関数を考えます。
この関数に型ヒントを付けるとき、あなたならどうしますか?

def repeat(x, n):
   return [x] * n

間違いの例

自分は以下のように型ヒントを付けていたときがありました。同じような人もいるのではないでしょうか?
しかしながら、これは間違っています。
なぜなら返り値のlistの要素としてint型とfloat型の両方を持ちうるからです。
実際には引数の型によりどちらか一方に決まるはずです。

from typing import List, Union


def repeat(x: Union[int, float], n: int) -> List[Union[int, float]]:
   return [x] * n

正しい型ヒントだが冗長な例

引数の型毎に関数を実装すればどうでしょうか?型ヒントは正しいですが同じような処理を型ごとに定義するのは望ましく有りません。

from typing import List


def repeat_int(x: int, n: int) -> List[int]:
   return [x] * n


def repeat_float(x: float, n: int) -> List[float]:
   return [x] * n

正しい型ヒント

こういった問題を解決するための機能としてジェネリクスがあります。
以下の例ではTという名前の型変数を利用して型ヒントを付けています。
このTは先程のユニオンと違い、実際には型として不定になっています。
そして、このTを利用するタイミング、つまり関数の型を利用するタイミングで型が決まります。

from typing import List, TypeVar

T = TypeVar('T', int, float) # Xはintもしくはfloat型である。


def repeat(x: T, n: int) -> List[T]:
    return [x] * n

そのため、xがint型であれば返り値もint型のリスト、xがfloat型であれば返り値もfloat型のリストとして決定されます。

list1 = repeat(1, 100)
# list1: List[int] として型推論される。

list2 = repeat(0.1, 100)
# list2: List[float] として型推論される。

 

おわりに

この記事では型変数を利用した型ヒントについて紹介しました。
ジェネリクスのような機能を理解することでPythonの型ヒントの使い方の幅が広がると思います。
mypyなど利用する際には取り入れてみてはいかがでしょうか?

Y.N
GRIでデータ分析やアルゴリズム開発、ForecastFlowの開発に携わっています。