実行環境
- Python 3.9.7
- dataclasses-json 0.5.6
はじめに
jsonやdictからPythonのデータクラスを生成するために、dataclasses-json を使っています。
以下のように、Animalクラスのdetailフィールドにデフォルト値が設定されている場合、detailキーが存在しないdictからAnimalクラスのインスタンスを生成できます。
@dataclass_json
@dataclass
class Animal:
name: str
detail: Optional[str] = None
Animal.from_dict({"name": "dog"})
# Animal(name='dog', detail=None)
問題提起
今、Animalクラスがあります。
@dataclass_json
@dataclass
class Animal:
name: str
detail: Optional[str] = None
Animalクラスにtypeという必須フィールドを、後から追加する必要があることが分かりました。
Animal("human", "foo")のように位置引数を指定してAnimalクラスのインスタンスを生成する場合もあるので、Animalクラスのフィールドの順番は変えたくありません。したがって、Animalクラスの最後尾にtypeフィールドを追加してみます。
なんと、最後尾にtypeフィールドを追加すると、TypeErrorが発生しました。デフォルト値を受け取る引数より後ろの引数は、必ずデフォルト値を設定する必要があるためです。
@dataclass_json
@dataclass
class Animal:
name: str
detail: Optional[str] = None
type: str
# TypeError: non-default argument 'type' follows default argument
TypeErrorを回避するため、detailフィールドのデフォルト値を除去しました。これで、Animalクラスは無事定義できました。
@dataclass_json
@dataclass
class Animal:
name: str
detail: Optional[str]
type: str
しかし、デフォルト値が設定されていないため、以下のようにdetailキーが存在しないdictからAnimalクラスのインスタンスを生成できません。これでは不便です。
Animal.from_dict({"name":"human", "type": "Mammal"})
# KeyError: 'detail'
解決策
from_dictメソッドにinfer_missing=Trueを指定すれば、detailキーが存在しないdictからAnimalクラスのインスタンスを生成できます。
Animal.from_dict({"name":"human", "type": "Mammal"},infer_missing=True)
# Animal(name='human', detail=None, type='Mammal')
ただしinfer_missing=Trueを指定すると、Optionalでない型のフィールドが指定されていないときでも、Animalクラスのインスタンスを生成できてしまいます。
Animal.from_dict({"name":"human"}, infer_missing=True)
# RuntimeWarning: Missing value of non-optional type type detected when decoding Animal and was defaulted to None by infer_missing=True. Set infer_missing=False (the default) to prevent this behavior.
# Animal(name='human', detail=None, type=None)
そういった理由のためか、公式サイトではinfer_missing=Trueを指定することより、デフォルト値を設定することを推奨しています。
Personally I recommend you leverage dataclass defaults rather than using infer_missing, but if for some reason you need to decouple the behavior of JSON decoding from the field's default value, this will allow you to do so.