実行環境
- Python3.8.6
- dataclasses-json 0.4.5
やりたいこと
dataclasses-jsonを使って、データクラスからdictに変換したいです。
何が起きたのか
.schema().dump(...)を使って、データクラスPersonのlistを、dictのlistに変換します。
foo.pyでは、型ヒントにlistやdictなどを使うため、ファイルの先頭でfrom __future__ import annotationsを記載しました。python3.7以降で型ヒントにlistやdictを使う場合は、先頭にfrom __future__ import annotationsを記述する必要があります。
from __future__ import annotations
from dataclasses import dataclass
from dataclasses_json import dataclass_json
@dataclass_json
@dataclass
class Person:
name: str
people = [Person("Alice")]
dict_people = Person.schema().dump(people, many=True)
print(dict_people)
foo.pyを実行すると、UserWarning: Unknown type str at Person.nameという警告が発生しました。
$ python foo.py
/home/vagrant/.pyenv/versions/3.8.6/lib/python3.8/site-packages/dataclasses_json/mm.py:270: UserWarning: Unknown type str at Person.name: str It's advised to pass the correct marshmallow type to `mm_field`.
warnings.warn(
[{'name': 'Alice'}]
警告が発生する条件を調べる
from __future__ import annotationsを取り除けば、警告は発生しませんでした。
Python3.9.7でも警告は発生しました。
警告が発生する原因
警告が発生している関数は以下の部分です。
def build_type(type_, options, mixin, field, cls):
def inner(type_, options):
...
origin = getattr(type_, '__origin__', type_)
args = [inner(a, {}) for a in getattr(type_, '__args__', []) if
a is not type(None)]
if _is_optional(type_):
options["allow_none"] = True
if origin in TYPES:
return TYPES[origin](*args, **options)
if _issubclass_safe(origin, Enum):
return EnumField(enum=origin, by_value=True, *args, **options)
if is_union_type(type_):
union_types = [a for a in getattr(type_, '__args__', []) if
a is not type(None)]
union_desc = dict(zip(union_types, args))
return _UnionField(union_desc, cls, field, **options)
warnings.warn(
f"Unknown type {type_} at {cls.__name__}.{field.name}: {field.type} "
f"It's advised to pass the correct marshmallow type to `mm_field`.")
return fields.Field(**options)
return inner(type_, options)
本来if origin in TYPES:文の中を通過するはずなのですが、この条件が通らないためwarnings.warnを呼び出しているようです。
変数originをprint(f"{origin=}, {type(origin)=}")で出力してみると、以下の結果になりました。
# `from __future__ import annotations` あり
origin='str', type(origin)=<class 'str'>
# `from __future__ import annotations` なし
origin=<class 'str'>, type(origin)=<class 'type'>
from __future__ import annotationsを付けると、Person.nameに対応するoriginが'str'になってしまうことにより、警告が発生することが分かりました。
from __future__ import annotationsの有無で、なぜ変数originが変わってしまうのかは、分かりませんでした。
回避策
警告メッセージに従いmm_fieldを指定すれば、警告は出なくなります。
が、この書き方は冗長なので、採用しない方がよいです。大人しくtyping.Listを使った方がよいでしょう。
from __future__ import annotations
from dataclasses import dataclass, field
from dataclasses_json import dataclass_json, config
from marshmallow import fields
@dataclass_json
@dataclass
class Person:
name: str = field(metadata=config(mm_field=fields.Str()))