mypy に怒られない unpack（NamedTuple のススメ）

下のような関数があったとする。

```python
def intro(age: int, name: str) -> None:
	print(f"{name} is {age} years old.")
    return
```

この関数を呼び出す時に、引数をいちいち指定するのが面倒臭かったとする。
（あまりにも引数の数が多いとか、他の所で作ったデータをそのまま入れたい時とか）

そういう時に、下記のようなことをすると思う。
しかも mypy さんに怒られないように、とても丁寧にコードを書くと、こうなる。

```python
person: Dict[str, Union[str, int]] = {
	"name": "Taro",
	"age": 3,
}
intro(**person)
```

何も悪いことはない。普通に動く。
でも肝心の mypy さんにメチャ怒られる。メチャ忖度してるのに！

```
> mypy hoge.py
hoge.py:5: error: Argument 1 to "intro" has incompatible type "**Dict[str, Union[str, int]]"; expected "int"  [arg-type]
hoge.py:5: error: Argument 1 to "intro" has incompatible type "**Dict[str, Union[str, int]]"; expected "str"  [arg-type]
Found 2 errors in 1 file (checked 1 source file)
```

なんでや。

で、まじで「なんでや」を調べたら泥沼にはまったけど、その泥沼は良い沼だったという話。

（まぁ、この例にはオチがあるんだけど）

以下、私の解釈を載せるけど、あくまで私個人の解釈なので間違っているかもしれない。（逃げ）

# unpack の話

まずは mypy に怒られている `**person` の所。アスタリスクの部分。

言うまでもなく unpack で、person という辞書の中身を展開してる。Python の教科書のワリと最初の方に載っているはず。でも、よく考えると unpack って意味があまり分からない。

調べまくって考えまくった結果、この `*` 演算子（`*-operator`）[^1]は、関数への引数でしか許されない特殊な演算子だ、という結論になった。単項演算子で、もちろん２項演算子である掛け算の `*` (mul) とは違う。

演算子と言っても、[言語リファレンスの演算子の項目](https://docs.python.org/ja/3/reference/lexical_analysis.html#operators) にも [標準ライブラリリファレンスの組み込み型の項目](https://docs.python.org/ja/3/library/stdtypes.html) にも記載がない。

演算子というより、その後に続く変数と合わせて `starred expression` 、すなわち式（expression）だと理解した方が正しいのだと思う。で、その式は関数の引数の部分でしか許されない、と。

関数の引数でしか許されないというのは簡単に調べることが出来る。

```python
>>> a = [1, 2]
>>> b = *a
  File "<stdin>", line 1
SyntaxError: can't use starred expression here
```

特に意味はないけど、なんか `b` という変数に `a` の中の値が展開されて入っていてもおかしくない気がする。でもダメ。こんな所では使えない。

[^1]: 演算子という記述があるのは、[Python 公式ドキュメント](https://docs.python.org/ja/3/)を調べた限り、チュートリアルの [4.8.5. 引数リストのアンパック](https://docs.python.org/ja/3/tutorial/controlflow.html#unpacking-argument-lists)のみ。

## unpack と暗黙的な Tuple

混乱するのは、この starrd expression を使わなくても unpack は出来るっぽいこと。

```python
>>> a = [1, 2]
>>> b, c = a
>>> d = a
>>> print(f"{a} : {b} : {c} : {d}")
[1, 2] : 1 : 2 : [1, 2]
```

２行目のように、式の左辺に右辺の中身の数と同じ数の変数があれば、自動的に unpack できるように「見える」。もちろん３行目のように変数が１個だけだったら List 全体が代入される。

この場合、右辺の中身の数と左辺の変数の数が違うと怒られる。

```python
>>> a = [1, 2, 3]

>>> b, c = a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 2)

>>> b, c, d, e = a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected 4, got 3)
```

この場合もエラーメッセージに `unpack` と出てるので混乱する。

じゃあこれは何なのかと考えると、何の括弧も無く `,` という[デリミタ](https://docs.python.org/ja/3/reference/lexical_analysis.html#delimiters)で変数を繋げると、Python は暗黙的にそれらを Tuple だと判断しているのだ。

```python
>>> def dummy():
...     return 1, 2

>>> a = dummy()
>>> type(a)
<class 'tuple'>
```

だからさっきのは正確には unpack ではなく、Tuple に List の内容を代入している。そりゃ数が合わないと怒られるよ。

```python
>>> a = [1, 2]
>>> b, c = a
>>> (b, c) = a  # ２行目は本当はこういうことをしている
```

だから、unpack というのは厳密には `starred expression` でしか行われていない（はず）。

## starred expression の定義、関数の定義

じゃあこの `starred expression` はどこで説明されているかというと、[言語リファレンスの 8.7 章、関数定義](https://docs.python.org/ja/3/reference/compound_stmts.html#function-definitions)と [言語リファレンスの 6.3.4章、呼び出し（call）](https://docs.python.org/ja/3/reference/expressions.html#calls)の所になる。やはり `starred expression` は関数の定義の一部なのだ。

で、そこを読めば出てくるんだけど、非常に分かりにくい。しかも文法として（書き方が）載っているだけで、意味は分からない。一番わかりやすいのが [チュートリアルの 4.8章、関数定義についてもう少し](https://docs.python.org/ja/3/tutorial/controlflow.html#more-on-defining-functions) になる。

この章をまとめると、Python での関数の定義のやり方は以下になる。[^2]

```python
def func(pos..., /, pos_or_kwd..., *, kwd...):
```

[^2]: Python で `...` は [Ellipsis](https://docs.python.org/ja/3/library/constants.html#Ellipsis)という組み込み定数だが、まぁここは勘弁して欲しい

そもそも関数の引数（Argument）には３種類ある。

* Positional Argument（位置引数）
    * 関数の引数として何番目に書かれたかによって、呼び出された関数の変数として格納される引数
    * 名前付きのキーワード形式（`pos=1`）で書いてはダメで、必ず値だけを入れなければならない
* Keyword Argument（キーワード引数）
    * 何番目に書かれたかは全く関係なく、キーワード形式の名前で格納される変数が決まる引数
* Positional or Keyword Argument
    * キーワード形式で与えられればその名前をもつ変数に、値が直接入っていたらその順番に書かれている変数として格納される引数

これら３つの引数の種類を見分けるのは、`/` と `*` というキーワードのみ。

* `/` の前にある引数はすべて Positional Argument。すなわち名前付きでは受け入れられない
    * ただし `/` をつける必要はなく、その場合はすべての引数が Keyword もしくは Positional or Keyword
* `*` の後にある引数はすべて Keyword Argument。すなわち名前付きでないと受け入れられない
    * ただし `*` をつける必要はなく、その場合はすべての引数が Positional もしくは Positional or Keyword

私は、デフォルト値を設定すると Keyword Argument になると思っていた。勘違いだった。見た目だけでは本当に分からないんだ。ただ、デフォルト値をつけた Argument より後の Argument は、全部デフォルト値をつけなければならない。

```python
>>> def func(a=0, /):
...     print(a)

>>> func()
0

>>> func(a=1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: func() got some positional-only arguments passed as keyword arguments: 'a'

>>> def func(a=0, /, b):
  File "<stdin>", line 1
    def func(a=0, /, b):
                     ^
SyntaxError: non-default argument follows default argument
```

そしていよいよ `starred expression`。（ここまで来るまでが長過ぎる）

`starred expression` を含む関数の定義は以下になる。

```python
def func(pos..., /, pos_or_kwd..., *args, **kwargs)
```

* `*` というキーワードが含まれると、`starred exporession` は SyntaxError になる
* `*args` という形で与えられた変数 `args` には、それまでの Positional Argument には入り切らなかった Positional Argument（名前がついてない Argument）が全部入ってくる
    * 型は Tuple
* `**kwargs` という形で与えられた変数 `kwargs` には、それまでの Keyword Argument には名前が無かった Keyword Arguement が全部入ってくる
    * 型は Dict

```python
>>> def func(a, b, *args):
...     print(type(args))
...     print(args)

>>> func(1, 2, 3, 4, 5)
<class 'tuple'>
(3, 4, 5)

>>> def func(a, b, **kwargs):
...     print(type(kwargs))
...     print(kwargs)

>>> func(a=1, b=2, c=3, d=4, e=5)
<class 'dict'>
{'c': 3, 'd': 4, 'e': 5}
```

まぁ、「知ってた」って言われると思うが。

## starred expression にも Type hint を付け…たい

この記事の目的はとにかく mypy さんに媚を売ることなので、律儀に starred expression にも Type hint を付けていきたい。で、「**たぶん**」こうやればいいのだろう。（間違っている可能性が大）

```python
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import typing as t
import typing_extensions as te


class IntroKwargs(t.TypedDict):
    fullname: str
    birthyear: int


def intro(
    age: int,
    name: str,
    *args: te.Unpack[t.Tuple[int, ...]],
    **kwargs: te.Unpack[IntroKwargs],
) -> None:
    print(f"{name} is {age} years old.")
    print(args)
    print(kwargs)
    return


def main() -> None:
    intro("Taro", 3, 1, 2, fullname="Yamada Taro", birthyear=2020)
    return
```

~~で、「たぶん」って言ってるのは、これ、**動かない**からだ。もちろんプログラム的には動くけど、mypy が違う意味で通らない。~~

* Unpack が本格的に typing ライブラリに組み込まれるのは Python 3.11 からで、それまでは上のように `typing_extensions` から持ってこなきゃいけない
* Python 3.11 にして typing から import したとしても、mypy が Unpack に対応していない
    * mypy に `--enable-incomplete-feature=Unpack` というオプションをつけなければならない
* `*args` はオプションつければ何とかなるけど、`**kwargs` のチェックで mypy が落ちる
    * Unpack は必ず引数をひとつつけなきゃいけない
        * `*args` は Tuple、`**kwargs` は TypedDict
    * ただし、TypedDict をそのまま使う（`Unpack[TypedDict]` とする）と、`error: Variable "typing.TypedDict" is not valid as a type [valid-type]` というエラーになる
        * typing.TypedDict や typing.NamedTuple は typing の中でも特殊で、型というよりかは class に近い
    * ~~じゃあ上記のようにちゃんと作ると、INTERNAL ERROR で落ちる~~
        * ~~github からソースコード持ってきてコンパイルしてみてくれ、って言われる~~

ということで、starred expression に type hint を付けるには、まだ時代が早すぎたんだ。（2023年2月8日）

まぁこんな泥沼にハマってしまった訳だが、決して悪いことばかりじゃない。

Python 3.11 から、続々と面白そうなヤツが typing に組み込まれていくようだ。Never, NoReturn, Self なんてコードの可読性がさらに向上しそうだし、今まで無理やり raise Exception していた所が自然な感じになりそう。

さらに[こんな記事](https://gihyo.jp/article/2022/09/monthly-python-2209)もあって、Python でどんどん type-safe なプログラミングができそうである。Python 3.11 がデフォルトになってくると、また違った世界が拓けてくるのではないか。

ただし、何度も言うように、「**まだ早い**」。

（追記：2023年2月9日）この記事を書いている間に mypy が 0.991 → 1.0.0 にバージョンアップして、上記の mypy が成功するようになった！こんなことってある？！！でも `--enable-incomplete-feature` のオプションが必要なので、**まだ早い**には変わりがない。

### （余談の余談）ローカル変数の型宣言

というように、mypy 沼にハマっている私だが、ちょっと困ったことがあった。以下のような場合である。

```python
if flag is True:
	result: int = func_flag()
else:
	result: int = func_noflag()
```

`result` ってのはここで初めて出てくるローカル変数なので、Type hint 付けなきゃいけない。で、上のようにすると mypy 様が「２行目でもう result に int ってつけてるから、４行目でつけんじゃねぇよ」って言ってお怒りになられる。素直に４行目のヒントを消せば問題ないのだが、釈然としない。

たぶん最も良い方法は、「変数の宣言時には必ず初期値を入れて初期化する」だ。初期化することで意図しないバグが潰せたりもするので、mypy 様はたぶんそういうことを仰ってる。正しくて正しくて震える。

```python
result: int = 0
if flag is True:
	result = func_flag()
else:
	result = func_noflag()
```

でも、どうしても初期値が無いこともあるだろう。この `= 0` がどうしても入れられない。

そんな時はこうすれば良い。

```python
result: int
if flag is True:
	result = func_flag()
else:
	resutl = func_noflag()
```

当たり前？いや、俺、すげービックリしたんだが。マジで目から鱗が落ちた。

考えてみりゃ、class 変数では死ぬほどこんなことしてる。でもそれがローカル変数でも出来るとは思わなかった。なんだろな、伝わらないかな、この驚き。

閑話休題すぎた。時を戻そう。

# 手当たり次第に unpack してみる

呼び出される側の関数の `starred expression ` についてはだいたい分かったが、じゃあ呼び出す側の unpack とは何なのか。それを探るために、[組み込み型](https://docs.python.org/ja/3/library/stdtypes.html)・[collections](https://docs.python.org/ja/3/library/collections.html)・[typing](https://docs.python.org/ja/3/library/typing.html) を読み込んで、思いつく限りの型を unpack してみることにした。たぶんこんな馬鹿なことするの俺しかいない。

で、今回もすでにプログラムは書いてます。

https://github.com/tetutaro/mypy_safe_unpack

具体的には、変数を複数（`name` と `age`）持てるタイプの型のインスタンス `person` を作り、それを `print(person)`, `print(*person)`, `print(**person)` してみたらどうなるのか。試した型は以下。

* `Tuple`
* `List`
* `Set`
* `Dict`
* `collections.namedtuple`
* `typing.NamedTuple`
    * NamedTuple を継承した class を作り、そのインスタンスを使う
* `typing.NamedTuple._asdict()`
    * 上記のインスタンスに `_asdict()` したものを使う
* `typing.TypedDict`
    * TypedDict を継承した class を作り、そのインスタンスを使う
* `class`
    * `name` と `age` という class 変数を持つ class を作り、そのインスタンスを使う
* `vars(class)`
    * 上記のインスタンスを `vars()` 関数にかけたものを使う
* `pydantic.BaseModel`
    * BaseModel を継承した class を作り、そのインスタンスを使う
* `pydantic.BaseModel.dict()`
    * 上記のインスタンスに `dict()` したものを使う

さすがの俺でも FrozenSet, collections.defaultdict, collections.OrderedDict は違うなと思ったので、やってない。

こうやって説明していても分からないと思うので、詳しくは[ソース](https://github.com/tetutaro/mypy_safe_unpack/blob/main/mypy_safe_unpack/unpack_everything.py)を参照してください。

結果。

|      personの型      |            `print(person)`            |       `print(*person)`        |           `print(**person)`           |
| :------------------: | :-----------------------------------: | :---------------------------: | :-----------------------------------: |
|       `Typle`        |              ('Taro', 3)              |            Taro 3             |     ERROR<br />"must be mapping"      |
|        `List`        |              ['Taro', 3]              |            Taro 3             |     ERROR<br />"must be mapping"      |
|        `Set`         |              {3, 'Taro'}              |            3 Taro             |     ERROR<br />"must be mapping"      |
|        `Dict`        |      {'name': 'Taro', 'age': 3}       |           name age            | ERROR<br />"invalid keyword argument" |
|     `namedtuple`     |      Person(name='Taro', age=3)       |            Taro 3             |     ERROR<br />"must be mapping"      |
|     `NamedTuple`     | NamedTuple_Person(name='Taro, age=3') |            Taro 3             |     ERROR<br />"must be mapping"      |
| `NamedTuple._asdict` |      {'name': 'Taro', 'age': 3}       |           name age            | ERROR<br />"invalid keyword argument" |
|     `TypedDict`      |      {'name': 'Taro', 'age': 3}       |           name age            | ERROR<br />"invalid keyword argument" |
|       `class`        |         <class_Person object>         | ERROR<br />"must be iterable" |     ERROR<br />"must be mapping"      |
|    `vars(class)`     |      {'name': 'Taro', 'age': 3}       |           name age            | ERROR<br />"invalid keyword argument" |
|     `BaseModel`      |           name='Taro' age=3           |  ('name', 'Taro') ('age', 3)  |     ERROR<br />"must be mapping"      |
|   `BaseModel.dict`   |      {'name': 'Taro', 'age': 3}       |           name age            | ERROR<br />"invalid keyword argument" |

当たり前だと思われるかもしれないけど、色々気付くことがある。

* 星ひとつの unpack（`*person`）が期待しているものは、class でエラーが出ているように、[Iterable](https://docs.python.org/ja/3/library/collections.abc.html#collections.abc.Iterable)
    * Iterable は collections.abc に属するものなので、`__subclasses__()` とかには出てこない
    * でも `iter()` で簡単に調べられる
        * `iter(list()) is not None` → `True`
    * 要するに、`__iter__` を提供しているので、for 文の目的として使えるヤツ
* 星ふたつの unpack（`**person`）が期待しているものは、list とかでエラーが出ているように、[Mapping](https://docs.python.org/ja/3/library/collections.abc.html?highlight=sequence#collections.abc.Mapping)
    * 同じくMapping も collections.abs に属する
    * `__iter__` に加え、`__getitem__` と `__len__` を提供しているもの
* Mapping は `__iter__` を提供しているので、Iterable になれてしまう
    * `*person` しても怒られない
* ただし、Mapping の星ひとつ unpack を見ると、key だけが出てきている
* これは Dict（やその仲間）がどうやって作られているかを表している
    * Dict は、そのインスタンスの中で key が一意だ
        * 同じ key に対して別の value を入れると、上書きされる
    * だから、Dict はその key を Set として持っている
        * だから Dict も Key も `{...}` で作ることが出来る
    * Set は、その中身は任意の変数だけど、Dict は、その中身は必ず Mapping
        * `{x: y}` で辞書になるが、この場合の `:` は [mapping](https://docs.python.org/ja/3/glossary.html#term-mapping) を表す（＝`__getitem__`）
    * そう言えば、Type hint も全部 `x: y` の形で表すけど、これも「対応」って意味なんだろうなぁ
        * [slicing](https://docs.python.org/ja/3/reference/expressions.html?highlight=slicing#slicings) や [lambda](https://docs.python.org/ja/3/reference/expressions.html?highlight=slicing#lambda) は見なかったことにしよう
        * [条件式](https://docs.python.org/ja/3/reference/expressions.html?highlight=slicing#conditional-expressions)って lambda だったんだ

というように、Python ってどうやって作られているのかという所まで少しは理解できるようになった。非常に興味深い。特に collections.abc なんて眺めていると、たぶん Python を作った人は脳みそのレベルが違うんだろうな、と思ってしまう。

# mypy に怒られない unpack

話がまた脇道にそれそうなのを強引に戻して、やっと今回の本命の話に。

今なら分かるが、実は一番最初の `intro()` という関数は、引数に `*` というキーワードを設定していないので、`name` も `age` も Positional Argument, Keyword Argument のどちらでも良い状態になっている。なので、名前を指定できない Set も Tuple も List も `*person` で関数に引数を与えられてしまう。（そしてわざと順番を逆にしているので `3 is Taro years old.` なんて変テコな文章が出力される）

折角なので、ちゃんと表にしてみよう。まずは Positional Artument の場合。インスタンスをそのまま入れた場合は、全部揃って引数の数が足りないエラー（missing 1 required positional argument）になるので、省略する。

|      personの型      |             `intro(*person)`             |      `intro(**person)`       |
| :------------------: | :--------------------------------------: | :--------------------------: |
|       `Typle`        |           3 is Taro years old            | ERROR<br />"must be mapping" |
|        `List`        |           3 is Taro years old            | ERROR<br />"must be mapping" |
|        `Set`         |           3 is Taro years old            | ERROR<br />"must be mapping" |
|        `Dict`        |          age is name years old           |     Taro is 3 years old      |
|     `namedtuple`     |           3 is Taro years old            | ERROR<br />"must be mapping" |
|     `NamedTuple`     |           3 is Taro years old            | ERROR<br />"must be mapping" |
| `NamedTuple._asdict` |          age is name years old           |     Taro is 3 years old      |
|     `TypedDict`      |          age is name years old           |     Taro is 3 years old      |
|       `class`        |      ERROR<br />"must be iterable"       | ERROR<br />"must be mapping" |
|    `vars(class)`     |          age is name years old           |     Taro is 3 years old      |
|     `BaseModel`      | ('age', 3) is ('name', 'Taro') years old | ERROR<br />"must be mapping" |
|   `BaseModel.dict`   |          age is name years old           |     Taro is 3 years old      |

要するに、上で "invalid keyword arguments" になっているものが、正常な（期待した）出力になっている。次に、ちゃんと `*` を引数の一番先頭に置いて、全部が Keyword Argument だとした場合。今度は `intro(*person)` が全部 "takes 0 positional arguments but X was given"という、Positional Keyward が与えられたエラーになるので、省略。

|      personの型      |      `intro(**person)`       |
| :------------------: | :--------------------------: |
|       `Typle`        | ERROR<br />"must be mapping" |
|        `List`        | ERROR<br />"must be mapping" |
|        `Set`         | ERROR<br />"must be mapping" |
|        `Dict`        |     Taro is 3 years old      |
|     `namedtuple`     | ERROR<br />"must be mapping" |
|     `NamedTuple`     | ERROR<br />"must be mapping" |
| `NamedTuple._asdict` |     Taro is 3 years old      |
|     `TypedDict`      |     Taro is 3 years old      |
|       `class`        | ERROR<br />"must be mapping" |
|    `vars(class)`     |     Taro is 3 years old      |
|     `BaseModel`      | ERROR<br />"must be mapping" |
|   `BaseModel.dict`   |     Taro is 3 years old      |

ということで、ちゃんと引数を与えられたのは、以下の５種類しかない。

* `Dict`
* `typing.NamedTuple._asdict()`
* `typing.TypedDict`
* `vars(class)`
* `pydantic.BaseModel.dict()`

そしてこの中で、mypy 様がお怒りになられないのは、下の４つである。

要するに、ちゃんと名前を与えられて、しかも変数毎に Type hint をちゃんと渡せるやつである。

そりゃそうだ。ここまでしないと分からなかったんかい、と言ってしまいそうな、単純な結果である。

# その中でのオススメ

`vars()` は、class 変数を参照して、その名前と型をローカル空間の Dict にしてくれるので、簡単に unpack できる便利関数である。私は argparse.ArgumentParser が parse_args() で生成する Namespace を引数に与える時に良く使う。

しかし、そのためだけに class を作るのは違う。そもそも class ってそういう風に使うものではない。Namespace みたいに、既にある時に使うのは良いと思うけど。

そうなってくると typing.NamedTuple か typing.TypedDict か pydantic.BaseModel になる。

[pydantic](https://docs.pydantic.dev/).[BaseModel](https://docs.pydantic.dev/usage/models/) は、外部とのデータのやり取りのプロトコル（API）を定義して、受信したデータの validation をしたり、送信するデータのチェックする用途には、非常に有用である。もうこれしか考えられないぐらい。（要するに [FastAPI](https://fastapi.tiangolo.com/ja/) だ）

ただし、内部で生成したデータを入れるには、ちょっとオーバースペックかな、と思う。全然使っても良いんだけど。

すると typing.NamedTuple か typing.TypedDict かになるんだけど、こうなると圧倒的に typing.[NamedTuple](https://docs.python.org/ja/3/library/typing.html?highlight=namedtuple#typing.NamedTuple) だろう。

なんてったって immutable である。これ以上に最高な点は無い。一旦データを入れたら変更できないので、余計なバグが発生しようがない。Type hint も付いているので、変なデータを入れることも出来ない。プログラム内部のデータ保持にはコレで決まり。

それに比べると typing.[TypedDict](https://docs.python.org/ja/3/library/typing.html?highlight=namedtuple#typing.TypedDict) は、プログラム的には利用価値は少なくなってくると思う。TypedDict 使うぐらいなら NamedTuple を使う。ただし、上の Unpack の定義に使ったように、[TypeAlias](https://docs.python.org/ja/3/library/typing.html?highlight=namedtuple#typing.TypeAlias) や [Annotated](https://docs.python.org/ja/3/library/typing.html?highlight=namedtuple#typing.Annotated) と併用して、内部のデータ構造を記述するのに使うんだろうなぁと思う。

まとめると、こうなる。

* 内部のデータ保持・受け渡し：typing.NamedTuple
* 外部とのデータやり取り・チェック：pydantic.BaseModel
* 既に class に保持している変数を再利用したい：vars(class)
* 複雑なデータ構造を記述する：typing.TypedDict

今回、ちょっとした mypy のエラーから Python の奥地まで調べちゃったけど、改めて Python には面白いことが一杯詰まっているし、まだまだ進化途中でこれから発展していくんだろうなと思った。Python 3.11 が defact standard になると、NamedTuple や TypedDict, TypeAlias を使って、Python と言えども強固で堅牢なサーバが構築できるようになるんじゃないかと。そんな未来。

# オチ

一番最初の例、こう書くと mypy さん、全然怒らない。下の３種類とも全然怒らない。

```python
person_1: Dict[str, Any] = {
	"name": "Jiro",
	"age": 4,
}
intro(**person_1)

person_2: Dict[Any, Any] = {
	"name": "Saburo",
	"age": 5,
}
intro(**person_2)

person_3: Dict = {
	"name": "Shiro",
	"age": 6,
}
intro(**person_3)
```

`Dict` は `Dict[Any, Any]` だと解釈されるし、`Any` は凄く強い免罪符で、何を渡しても mypy さんは許しちゃう。mypy さん意外と懐が深い。