🐍
Pydanticではミュータブルでもdefault, default_factoryのどっちでもよさそう
結論
pydantic
ではdefault
もdefault_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ではフィールドの初期値を設定するためにdefault
とdefault_factory
いう2つのプロパティが用意されています。今回の記事では、default
とdefault_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のデフォルト引数と同じように、配列のインスタンスが共有されているのを想像しました。
しかし、pydantic
はRust
で作られていたり、インスタンス生成後に期待通りになるというコンセプトで作られているからかdefault
でもdefault_factory
でも同じ結果で返却されました。
default_factory
の本来の使い方としては、uuid
を生成したり、処理時刻を生成したりするcallableを呼ぶために使用するようですが、配列くらいならそのままdefault
でも問題なさそうですね。
とはいえ、コードリーディングで混乱しないように次の使い方をしておくことにします。
- default
- イミュータブルな項目を渡す
- default_factory
- ミュータブルな項目を生成してから渡す
Discussion