🐍

Pydanticではミュータブルでもdefault, default_factoryのどっちでもよさそう

2024/08/27に公開

結論

pydanticではdefaultdefault_factoryも同等の結果を返却しそう。

始めに

小ネタ記事。Pythonではデフォルト引数にミュータブルな値を指定したうえで、ミュータブルな操作を行うと、同じインスタンスを共有してしまいます。

def default_param(param: str, result:List[str]=[]) -> List[str]:
    result.append(param)
    return result

_ = default_param("1")
result = default_param("2")

print(result)
# "1"をappendしたresultインスタンスを共有しているので、['1', '2']が出力される
# ['1', '2']

PythonでFastAPI等のWebフレームワークを作る際には、pydanticというデータ構造化とデータバリデーションに優れたライブラリを使用しています。

そのpydanticではフィールドの初期値を設定するためにdefaultdefault_factoryいう2つのプロパティが用意されています。今回の記事では、defaultdefault_factoryの違いがないかを素振りします。

環境

  • Python
    • 3.12.4
  • FastAPI
    • 0.112.1

実装

次のようなデータ構造を用意します。

from typing import List, Annotated

from pydantic import Field, BaseModel

class CustomArray(BaseModel):
    array: Annotated[List[str], Field(default=[])]
    array_factory: Annotated[List[str], Field(default_factory=list)]

APIから実行できる状態にしておきます。

@router.put("/test/array_factory", response_model=CustomArray)
async def test_array_factory():
    result = CustomArray()
    result2 = CustomArray()
    result.array.append("A")
    result.array_factory.append("A")
    result2.array.append("B")
    result2.array_factory.append("B")
    print(result)
    print(result2)
    _ = default_param("1")
    resultx = default_param("2")
    print(resultx)


    return result2

3回実行しました。

INFO:     127.0.0.1:60892 - "PUT /array/test/array_factory HTTP/1.1" 200 OK
array=['A'] array_factory=['A']
array=['B'] array_factory=['B']
['1', '2']
INFO:     127.0.0.1:60892 - "PUT /array/test/array_factory HTTP/1.1" 200 OK
array=['A'] array_factory=['A']
array=['B'] array_factory=['B']
['1', '2', '1', '2']
INFO:     127.0.0.1:60892 - "PUT /array/test/array_factory HTTP/1.1" 200 OK
array=['A'] array_factory=['A']
array=['B'] array_factory=['B']
['1', '2', '1', '2', '1', '2']

ソースコード

終わりに

個人的な期待値としてはPythonのデフォルト引数と同じように、配列のインスタンスが共有されているのを想像しました。

しかし、pydanticRustで作られていたり、インスタンス生成後に期待通りになるというコンセプトで作られているからかdefaultでもdefault_factoryでも同じ結果で返却されました。

default_factoryの本来の使い方としては、uuidを生成したり、処理時刻を生成したりするcallableを呼ぶために使用するようですが、配列くらいならそのままdefaultでも問題なさそうですね。

とはいえ、コードリーディングで混乱しないように次の使い方をしておくことにします。

  • default
    • イミュータブルな項目を渡す
  • default_factory
    • ミュータブルな項目を生成してから渡す

参考情報

Discussion