[Python] dictやlistの中身がUnion型の場合のmypyエラーの原因と対応策

# 問題
関数の引数として中身が`Union`型の`dict`や`list`を定義した場合に、関数の呼び出し部分でmypyの型エラーが発生しました。直感的にはこれらの型は包含関係にありそうですが、どうやらdictやlistの型は奥が深いようです。
```python
def func_dict(dic: dict[int|str, str]) -> None:
    pass

def func_list(ls: list[int|str]) -> None:
    pass

dic = {'a': 'b'}
ls = ['a', 'b']
func_dict(dic)
func_list(ls)
```

> error: Argument 1 to "func_dict" has incompatible type "dict[str, str]"; expected "dict[int | str, str]"  [arg-type]
error: Argument 1 to "func_list" has incompatible type "list[str]"; expected "list[int | str]"  [arg-type]


# 原因
結論から言うと、`str`と`int|str`など中身の型自体に包含関係があっても、それらを要素とする`dict`や`list`などの invariantなGenericsどうしは包含関係を失うことが原因です。以降、この詳細を解説していきます。

## Genericsについて
まず、Pythonの`dict`や`list`はGenericsを用いて表現されているコンテナの1つです。Genericsとは、データ型を表現する型の一部を変数化する機能です。これにより、要素に様々な型を定義できるようになっています。type hintを書くときに`list[]`のように`[]`を用いてかかれる型がGenericsにあたります。このコンテナを扱う上での大きな注意点は、**中身の型のsubtype関係を必ずしも引き継がないこと**です。具体的には、型Aが型Bのsubtype (以降 A ⊂ B とかく)だった場合でも、必ずしもGenerics[A] ⊂ Generics[B]ではないということです。この点が今回のエラーの発生要因となってくるので、以下で詳しく解説していきます。

## Genericsのsubtype関係
PEP483<sup>[1] </sup>によるとGenericsどうしのsubtype関係は以下の3つに分類されるそうです。
- **covariance** (共変)
    - 中身のサブタイプの親子関係を受け継ぐ
    -> `A ⊂ B`のとき `Generics[A] ⊂ Generics[B]`
    - 例: Sequence, Union, FrozensSet
- **contravariance** (反変)
    - 中身のサブタイプの親子関係を逆転した関係になる
    -> `A ⊂ B`のとき `Generics[A] ⊃ Generics[B]`
    - 例: Callable の argument type
- **invariance** (非変)
    - 中身のサブタイプ関係を完全に引き継がない
    -> `A ⊂ B` のとき`Generics[A] ⊂ Generics[B]`でも`Generics[A] ⊃ Generics[B]` でもない
    - 例: List, Dict

基本的に `Sequence` 等のimmutableな (値の追加や置換ができない)インターフェースを持つデータ型はcovarianceですが、`dict`や`list`などmutableなデータ型はinvarianceとなっています。`str`と`int|str`は subtype関係 ではないですが、同様の仕組みで、`dict`等の箱に入れることによって包含関係が失われると考えるのが自然だと思います。

# 解決方法の例
### 1. 引数に入れる変数の型を明示する<sup>[2] </sup>
引数に代入する変数の型を `dict[int|str, str]` などとアノテーションしてしまえば、関数で定義した型と一致するためエラーがでることはありません。ただ関数を呼び出す度に、型ヒントを確認したり型エイリアスをインポートする手間がかかるのがデメリットです。
```python
dic : dict[int|str, str] = {'a': 'b'}
ls : list[int|str] = ['a', 'b']
func_dict(dic)
func_list(ls)
```
### 2.dictやlist自体のUnion型で定義する
`Union` はcovarianceなGenericsなので、中身でなく`list`や`dict`自体の`Union`型で定義してしまえばmypyは通ります。ただ、とりうる型のバリエーションが増えると、かなり冗長になってしまいます。
```python
def func_dict(dic: dict[int, str] | dict[str, str]) -> None:
    pass
def func_list(ls: list[int] | list[str]) -> None:
    pass
```

### 3.中身の型を自作のGenericsで定義する
`int|str` は invariantな箱の中では int や str そのものとは違う型として認識されてしまう一方で、 `TypeVar('T', str, int)` という自作Genericsは、`int`や`str`そのものになれる変幻自在の型を意味するため、`dict`の中であってもmypyエラーが起きません。
```python
from typing import TypeVar

T = TypeVar('T', str, int)

def func_dict(dic: dict[T, str]) -> None:
    pass
def func_list(ls: list[T]) -> None:
    pass
```
### 4. dictやlistをcovariantなGenericsに置き換える<sup>[2] </sup>
`dict`や`list`の抽象基底クラスである`Mapping`や`Sequence`型はcovariantであるため、それに置き換えれば問題は解決します。しかし、それらは同時にimmutableもであるため、関数内で要素を追加・削除したい場合にはこの方法は使えません。
```python
from typing import Mapping, Sequence

def func_dict(dic: Mapping[str|int, str] -> None:
	pass
def func_list(ls: Sequence[str|int]) -> None:
    pass
```

# 参考
[1] PEP483 - The Theory of Type Hints, 'Covariance and Contravariance', https://peps.python.org/pep-0483/#covariance-and-contravariance
[2] mypy - Common issues and solutions, 'Invariance vs covariance', https://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance