実は python の type hint が 挙動を影響することができる

## 概略/TLDR
python の type hint は
- 「動作を一切影響しない」
- 「あくまでコードの可読性のため」

とは危ない**大勘違い**！

 pydantic などの package を使うと、type hint が プログラムの**実行時の動作を影響することができる**(type coercion)。
→ その挙動を抑えるにため、pydantic に strict mode が用意されている
→ ただ strict mode　が抑えないケースもあるので、とにかく「type hint は動作を影響できる」と認識しておく方が良い

## 背景 type hint について
python の type hint とは、「runtime の挙動を影響しないもの」とは思いガチ
だって、type hint を導入した　[PEP484](https://peps.python.org/pep-0484/) が type hint を「annotation」 と呼び、以下のように説明する：

> While these annotations are available at runtime through the usual __annotations__ attribute, no type checking happens at runtime. Instead, the proposal assumes the existence of a separate off-line type checker which users can run over their source code voluntarily.

これは正しいが、PEP484 の３段落目の言う通り、3rd party Libraries は hint を runtime で参照して良いので、「type hint はあくまで読者のため」とかは認識してはいけない！

## 投稿を書くきっかけ(ハマったエピソード)
- ある関数の戻り値を、pydantic の model にしている
- model は type が `SubModel` の field  `sub` を持っている
- 条件によって、`sub` の type を type hint 通りで設定したり、違う type (空辞書)で設定したりする

```
from pydantic import BaseModel


class SubModel(BaseModel):
    optional_field: int | None = None


class ReturnValueModel(BaseModel):
    sub: SubModel


def get_pydantic_model(sub_as_expected_type: bool) -> ReturnValueModel:
    if sub_as_expected_type:
        return ReturnValueModel(sub=SubModel())
    else:
        return ReturnValueModel(sub={})

```

### 勘違いして期待した挙動
sub を 空辞書と設定している場合は、戻り値の `sub` field が、 `{}`となること
→ type が `Sub` でなく、`dict` か `Object`

### 実際の挙動：
`sub` の　type が必ず `SubModel` となる
```
pydantic_model = get_pydantic_model(sub_as_expected_type=False)
print(type(pydantic_model.sub))
# OUTPUT: "<class '__main__.SubModel'>" 
```


## 説明
pydantic は、 [type coercion](https://docs.pydantic.dev/latest/why/#strict-lax) を行う。
（つまり、c 言語を書く人にとってとても恐ろしい**暗黙変換**を意図的にやっている)

可能な範囲内、model の field に与えられた値を、field の type に変換している。
- 例： `"1"` が `int` として定義されている field に渡しても良い
- 例：(上記のシナリ) **全ての field が optional、かつdefault が定義されている** model (例では `SubModel`) である **field** (`sub`) に、**空辞書**にを設定して良い 

### type coercion を避けたいなら、strict mode は対応可能 ※ある程度
pydantic は [strict mode](https://docs.pydantic.dev/latest/concepts/strict_mode/) という機能がついており、そを使えば大半の type coercion を抑制することができる。

色んな使い方がある：
#### **field 単位**で設定できる: 以下の抜粋を pydantic の [document](https://docs.pydantic.dev/latest/concepts/strict_mode/) から引用：
  ```
  from pydantic import BaseModel, Field, ValidationError

  class AnotherUser(BaseModel):
      name: str
      age: int = Field(strict=True)
      n_pets: int

  ```

  OR
  ```
  from typing_extensions import Annotated
  from pydantic import BaseModel, Strict, ValidationError
  class User(BaseModel):
    name: str
    age: int
    is_active: Annotated[bool, Strict()]

  ```
  
#### **model** 単位で設定できる: [ConfigDict](https://docs.pydantic.dev/latest/concepts/strict_mode/#strict-mode-with-configdict)

### ただし、上記書いた例のような場合は、strict モードが効かない
strict mode にしても、以下の type coercion が行われる
- JSON からの validation※ における UUID type のfield は、 `str` が変換される (pydantic の [doc](https://docs.pydantic.dev/latest/concepts/strict_mode/#type-coercions-in-strict-mode) の例を参照)
- 例：(上記のシナリ) **全ての field が optional、かつdefault が定義されている** model (例では `SubModel`) である **field** (`sub`) に、**空辞書**が model に変換される

※ 「JSON からの validation」は、`Model.model_validate_json()`で行った validation。
(`Model.model_validate_json`の返却ちが Model の instance)

## 結論
- standard library のみを使ってれば、type hint 実行時の動作を影響しない
- 3rd party libraries は type hinto を runtime 参照して良いので、「type hint は影響ない」とは誤っており、バッグを招く認識
- pydantic の type hint による type coercion はある程度抑えられるが、どうしても避けられないケースがるので、留意する方が良い