0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

概略/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 が 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 の3段落目の言う通り、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 でなく、dictObject

実際の挙動:

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 を行う。
(つまり、c 言語を書く人にとってとても恐ろしい暗黙変換を意図的にやっている)

可能な範囲内、model の field に与えられた値を、field の type に変換している。

  • 例: "1"int として定義されている field に渡しても良い
  • 例:(上記のシナリ) 全ての field が optional、かつdefault が定義されている model (例では SubModel) である field (sub) に、空辞書にを設定して良い

type coercion を避けたいなら、strict mode は対応可能 ※ある程度

pydantic は strict mode という機能がついており、そを使えば大半の type coercion を抑制することができる。

色んな使い方がある:

field 単位で設定できる: 以下の抜粋を pydantic の document から引用:

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

ただし、上記書いた例のような場合は、strict モードが効かない

strict mode にしても、以下の type coercion が行われる

  • JSON からの validation※ における UUID type のfield は、 str が変換される (pydantic の doc の例を参照)
  • 例:(上記のシナリ) 全ての 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 はある程度抑えられるが、どうしても避けられないケースがるので、留意する方が良い
0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?