ユーニックス総合研究所

  • home
  • archives
  • python-kata-shitei

Pythonの型指定のやり方【型ヒント, 3.10以降】

  • 作成日: 2023-07-10
  • 更新日: 2023-12-24
  • カテゴリ: Python

Pythonの型指定のやり方

最近のPythonは型指定に力を入れていてどんどんバージョンアップされています。
もともとPythonには型指定などなく、かなり自由な記法だったのですが、それが不便だと言う意見があり型指定の機能が盛り込まれるようになったのではないかと推測できます。

型指定についてはtypingモジュールに機能がまとめられていますが、このモジュールはかなり仕様が複雑です。
なぜシンプルなPythonにここまで型をこじつけようとするのかと言うと、そういう需要があるからですが、正直ここまで仕様が膨れ上がっていると覚えるのも大変かと思います。

この記事では最近のPythonの型指定のやり方を解説します。
できるだけ簡単に解説することを目指します。

関連記事
Djangoでオブジェクトを一括作成・更新【bulk_create, bulk_update】
DjangoのModel.objects.filter()の使い方【QuerySet】
Djangoのmodelのcreate()の使い方【Python】
Django入門: ルートの設定 ~ 簡単な一行掲示板アプリを作る その4【Windows10】
NumPyのappend()の使い方: 配列の末尾に要素を追加
Numpyのarangeの使い方: 指定範囲の数列を生成する
Python3でYoutube Data APIを使ってキーワード検索する
PythonからC言語(my.puts)を呼び出して実行する

型ヒント

変数には型ヒントを指定できます。

x: int = 1  
y: str = 'hello'  

コロン(:)のあとのintstrが型ヒントです。
関数の引数や返り値にも指定できます。

def func(x: int, y: str) -> str:  
    return x * y  

print(func(10, 'hello'))  
# hellohellohellohellohellohellohellohellohellohello  

上記の場合、-> strというのが返り値の型ヒントです。

これらの型ヒントには強制力はなく、実行時に型チェックはされません。
型チェックをしたい場合はmypyなどを使います。

mypyによる型チェック

mypyをインストールします。

$ pip install mypy  

型チェックをしたいファイルをmypyで実行します。

$ mypy main.py  

型指定に問題が無ければSuccessと表示されます。

Success: no issues found in 1 source file  

問題がある場合は以下のようなエラーになります。

main.py:5: error: Argument 1 to "MyNumber" has incompatible type "int"; expected "Number"  [arg-type]  
Found 1 error in 1 file (checked 1 source file)  

Union(aまたはb)

Python3.10より前はaまたはbという型を表現するときはUnionを使っていました。
これは

from typing import Union  

x: Union[int, str] = 1  

上記のようなコードになります。
Union[int, str]というのは「intstrかのどちらか」という意味の型ヒントになります。

Python3.10以降は次のような書き方ができるようになりました。

x: int | str = 1  

リスト、タプル、辞書の型ヒント

リストやタプル、辞書については以下のように型ヒントを書けます。

x: list[int] = [1, 2]  
y: tuple[float, float] = (1.2, 3.4)  
z: dict[str, int | str] = {'a': 1, 'b': 'hello'}  

list[int]intは複数可ですが、tuple[float, float]はタプルの値が2つまでになります。
dictの場合は値はint | strintstrのどちらかという指定になります。

型エイリアス

型のエイリアス(別名)です。
たとえばfloatを持つlistを型として定義したい場合は

Vector = list[float]  
x: Vector = [1.2, 2.3]  

こういう感じでVectorを定義します。
またTypeAliasを使う場合は

from typing import TypeAlias  

Vector: TypeAlias = list[float]  

のように定義します。

新しい型の定義(NewType)

新しい型を定義したい場合はNewTypeを使います。

from typing import NewType  

Number = NewType('Number', int)  
x = Number(123)  
print(x)  # 123  

上記の場合、Numberという型を新しく定義しています。
Number型の実体はint型になっています。

NewTypeした型からさらに型を定義することも可能です。

from typing import NewType  

Number = NewType('Number', int)  
MyNumber = NewType('MyNumber', Number)  
x = MyNumber(Number(123))  
print(x)  # 123  

呼び出し可能なオブジェクトの型ヒント

関数のように呼び出しが可能なオブジェクトにはCallableという型ヒントを付けられます。

from collections.abc import Callable  

def func(x: int, y: str) -> str:  
    return x * y  

fn: Callable[[int, str], str] = func  

このCallableは何ともわかりづらいですが、以下のような書式になります。

Callable[[第1引数の型, 第2引数の型, ...], 返り値の型]  

TypeVar

型変数というのでジェネリクスなコードを書くことができます。

from typing import TypeVar  

T = TypeVar('T')  # 型変数のTを定義  

def func(l: list[T]) -> T:  
    return l[0]  

print(func([1, 2, 3]))  # 1  

この型変数はNewTypeとは区別されます。
この違いは以下のコードを見るとわかりやすいと思います。

from typing import NewType  

T = NewType('T', int)  # 型変数のTを定義  

def func(l: list[T]) -> T:  
    return l[0]  

print(func([1, 2, 3]))  # 1  

上記のコードをmypyにかけると以下のエラーになります。

main.py:8: error: List item 0 has incompatible type "int"; expected "T"  [list-item]  
main.py:8: error: List item 1 has incompatible type "int"; expected "T"  [list-item]  
main.py:8: error: List item 2 has incompatible type "int"; expected "T"  [list-item]  
Found 3 errors in 1 file (checked 1 source file)  

このエラーの意味するところはNewTypeintとは別の型として区別されていることです。
一方、TypeVarの場合はintを許容しています。
NewTypemypy上で厳密に型判定されますが、TypeVarはジェネリクス的に判定されるということだと思います。

TypeVarも型を指定できますが、これは許容する型を制限するために指定します。

from typing import TypeVar  

T = TypeVar('T', int, str)  # intまたはstrを許容  

def func(l: list[T]) -> T:  
    return l[0]  

print(func([1, 2, 3]))  # 1  
print(func(['a', 'b', 'c']))  # a  

# print(func([1, 'a']))  # 複合はエラーになる  
# main.py:10: error: Value of type variable "T" of "func" cannot be "object"  [type-var]  

# print(func([1.2]))  # 許容していない型  
# main.py:14: error: Value of type variable "T" of "func" cannot be "float"  [type-var]  

Any型

Anyはなんでもありな型です。

from typing import Any  

a: Any = None  
a = []  # OK  
a = 2  # OK  

s: str = ''  
s = a  # OK  

おわりに

typingにはこの他にもたくさんの機能があり、それらを全部紹介するのは無理です。
今回は目立っている機能を紹介しました。
なにか参考になれば幸いです。

🦝 < 頭がフットーしそうだよぉ!

🦝 < 型に縛られるぅ!