0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

dataclasses-json:`from __future__ import annotations`がある状態で`.schema().dump()`を実行すると、UserWarningが発生する

Last updated at Posted at 2022-01-26

実行環境

  • Python3.8.6
  • dataclasses-json 0.4.5

やりたいこと

dataclasses-jsonを使って、データクラスからdictに変換したいです。

何が起きたのか

.schema().dump(...)を使って、データクラスPersonのlistを、dictのlistに変換します。

foo.pyでは、型ヒントにlistdictなどを使うため、ファイルの先頭でfrom __future__ import annotationsを記載しました。python3.7以降で型ヒントにlistdictを使う場合は、先頭にfrom __future__ import annotationsを記述する必要があります。

foo.py
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でも警告は発生しました。

警告が発生する原因

警告が発生している関数は以下の部分です。

dataclasses_json/mm.py
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を呼び出しているようです。
変数originprint(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を使った方がよいでしょう。

foo.py
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()))
0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?