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では削除予定なので今からは使うべきではありません。
