実行環境
- 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()))