mypy 1.7.0で正式に実装されたキーワード引数を展開する時に アノテーションを付ける方法について。
- 以前までの展開されるキーワード引数に対するアノテーション
- TypedDictとUnpackを使ったアノテーション
- typing-extensions
- NotRequired
- TypedDictクラスを直接使う方法
以前までの展開されるキーワード引数に対するアノテーション
以前までは関数の引数で辞書を展開する形で書く場合、 以下のようなアノテーションのみが可能でした。
1 2 |
|
このように書くと、kw
自体はdict[str, str]
の型とみなされ、
適当なキーワード変数に対してすべてstr
であるとされる状態でした。
つまり、全て同じ型にすることしか出来ません。
かわりにstr | int
としたりAny
としたりすればすべての変数でいずれかでOKとされますが、
そうすると今度は逆に関数の中でそれぞれを扱うのが面倒になります。
TypedDictとUnpackを使ったアノテーション
今月リリースされたmypy 1.7.0で TypedDict と Unpack を使って個別に型を定義できるようにした場合でも解釈してくれるようになりました。
Python 3.10以降で 以下のように書くことが出来ます。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
これをmypyでチェックすると通りますが、
1
|
|
のようにして型を間違えると
1
|
|
と言った形でエラーを出します。
また、これがmypy 1.7より前だと
1
|
|
といった感じのエラーが出ます。
ただ、実はここにあるように、--enable-incomplete-feature=Unpack
を付ければ1.7以降と同様にUnpack
部分も解釈してくれます。
これは0.981で実装されています(1年前)。
なので今回から始めて使える様になった、というわけではありませんが デフォルトとして正式に導入された形になります。
typing-extensions
上のTypedDict
はpython 3.8で実装されていますが、
Unpack
の方は3.10で実装されました。
なので3.9以前のものだと上のようにfrom typing ...
の形で書くことは出来ません。
ですが、これらのtyping
の新しいモジュールを古いPythonでも使えるようにしてくれる
typing-extensions
というライブラリがあります。
これをpip
とかでインストールすると、Python 3.9で
1 2 |
|
と書き換えれば実行でき、mypyでもOKになります。
この書き方は3.10以降でも同じ様にtyping_extensions
を使っても書けるので
複数のPythonバージョンに対応したものを書きたい場合にはtyping_extensions
を使うとより広い範囲をカバーできます。
このtyping_extensions
ですが、
mypyやformatterのblack
が依存関係として持っているので、これらのツールをpip
で入れるとついでに入るので
使っている環境ですでに入っていることもあるかもしれません。
Poetryとかを使っている場合、開発用にのみインストールするものと通常インストールするものを分けている場合もあるかと思います。
mypyとかは開発時のみにインストールするようにするのが通常だと思いますが、
その場合、typing_extensions
も開発時のみにして
1 2 3 4 5 6 7 8 |
|
みたいな感じの設定だと、
開発関連のパッケージが入っていない実行環境ではtyping_extensions
がなくてエラーが起こります。
ただ実行時には実際に必要ないものなのでやはり開発時のみに入れておきたいところ。
これを実現するには以下のようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
TYPE_CHECKING
を使って型チェックするときにだけtyping_extensions
をimport
するようにすれば
通常実行時には使わないのでインストールして無くてもエラーになりません。
注意として、Unpack
を使って型付けをしている部分がそのままだと'Unpack' is not defined
のエラーを出してしまうので、
これを回避するために
from __future__ import annotations
を入れて実行時には見ない様にする必要があります。
NotRequired
TypedDictで作られた上のParam
ではa
とb
両方とも必ずあるべきものになっています。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
とすれば、
1
|
|
と言ったエラーを吐きます。
無くてもよくするためには NotRequired を使います。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
これだとOKです。
ただし、NotRequired
はPython 3.11で追加されているので
3.10以前ではtyping_extensions
を使う必要があります。
上と同じことを Required を使って書くことも出来ます。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
こちらの場合では必須なものにRequired
をつけていますが、
TypedDict
を使う際にtotal=False
オプションを付けていて、これによって
デフォルトではすべてがNotRequired
な状態になります。
Required
はPython3.8から導入されているのでこちらであればtype_extensions
を使わずとも古いバージョンでも使えます。
必須なものとそうでないものの数を見ながらスッキリする方で書けばよいかと。
TypedDictクラスを直接使う方法
TypedDict
を使った型を作る際、
上の様にTypedDict
を継承する形以外に以下の様な形でも同じ意味になります。
1 2 3 4 5 6 7 8 9 10 11 |
|
第二引数に辞書としてキーワードと型を与えるか、第二引数以降にキーワード引数として 変数とその型を渡すか。
ただし、後者の方はPython 3.11ですでに非推奨で3.13では削除予定なので今からは使うべきではありません。