Python3.10中marshmallow_dataclass的NewType无法转为dataclass问题求助
问题描述
升级至Python 3.10.6并同步更新依赖到marshmallow_dataclass 8.5.8后,原本在Python 3.9+marshmallow_dataclass 8.3.0环境可正常运行的NewType示例代码报错,提示IPv4 is not a dataclass and cannot be turned into one。
原代码
from marshmallow_dataclass import NewType, dataclass, List import marshmallow.validate IPv4 = NewType('IPv4', str, validate=marshmallow.validate.Regexp(r'^([0-9]{1,3}\.){3}[0-9]{1,3}$')) @dataclass class MyIps: ips: List[IPv4] MyIps.Schema().load({"ips": ["0.0.0.0", "grumble grumble"]})
报错信息
/home/username/.local/share/virtualenvs/venv_3.10/lib/python3.10/site-packages/marshmallow_dataclass/__init__.py:373: UserWarning: ****** WARNING ****** marshmallow_dataclass was called on the class <function NewType.<locals>.new_type at 0x7fb5a077fd90>, which is not a dataclass. It is going to try and convert the class into a dataclass, which may have undesirable side effects. To avoid this message, make sure all your classes and all the classes of their fields are either explicitly supported by marshmallow_dataclass, or define the schema explicitly using field(metadata=dict(marshmallow_field=...)). For more information, see https://github.com/lovasoa/marshmallow_dataclass/issues/51 ****** WARNING ****** warnings.warn( Traceback (most recent call last): File "/usr/lib/python3.10/dataclasses.py", line 1197, in fields fields = getattr(class_or_instance, _FIELDS) AttributeError: 'function' object has no attribute '__dataclass_fields__'. Did you mean: '__dataclass_params__'? During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/home/username/.local/share/virtualenvs/venv_3.10/lib/python3.10/site-packages/marshmallow_dataclass/__init__.py", line 370, in _internal_class_schema fields: Tuple[dataclasses.Field, ...] = dataclasses.fields(clazz) File "/usr/lib/python3.10/dataclasses.py", line 1199, in fields raise TypeError('must be called with a dataclass type or instance') TypeError: must be called with a dataclass type or instance During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/home/username/.local/share/virtualenvs/venv_3.10/lib/python3.10/site-packages/marshmallow_dataclass/__init__.py", line 384, in _internal_class_schema created_dataclass: type = dataclasses.dataclass(clazz) File "/usr/lib/python3.10/dataclasses.py", line 1185, in dataclass return wrap(cls) File "/usr/lib/python3.10/dataclasses.py", line 1176, in wrap return _process_class(cls, init, repr, eq, order, unsafe_hash, File "/usr/lib/python3.10/dataclasses.py", line 909, in _process_class for b in cls.__mro__[-1:0:-1]: AttributeError: 'function' object has no attribute '__mro__' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/home/username/testscript.python", line 9, in <module> MyIps.Schema().load({"ips": ["0.0.0.0", "grumble grumble"]}) File "/home/username/.local/share/virtualenvs/venv_3.10/lib/python3.10/site-packages/marshmallow_dataclass/lazy_class_attribute.py", line 33, in __get__ setattr(cls, self.name, self.func()) File "/home/username/.local/share/virtualenvs/venv_3.10/lib/python3.10/site-packages/marshmallow_dataclass/__init__.py", line 356, in class_schema return _internal_class_schema(clazz, base_schema, clazz_frame) File "/home/username/.local/share/virtualenvs/venv_3.10/lib/python3.10/site-packages/marshmallow_dataclass/__init__.py", line 402, in _internal_class_schema attributes.update( File "/home/username/.local/share/virtualenvs/venv_3.10/lib/python3.10/site-packages/marshmallow_dataclass/__init__.py", line 405, in <genexpr> field_for_schema( File "/home/username/.local/share/virtualenvs/venv_3.10/lib/python3.10/site-packages/marshmallow_dataclass/__init__.py", line 695, in field_for_schema generic_field = _field_for_generic_type(typ, base_schema, typ_frame, **metadata) File "/home/username/.local/share/virtualenvs/venv_3.10/lib/python3.10/site-packages/marshmallow_dataclass/__init__.py", line 503, in _field_for_generic_type child_type = field_for_schema( File "/home/username/.local/share/virtualenvs/venv_3.10/lib/python3.10/site-packages/marshmallow_dataclass/__init__.py", line 729, in field_for_schema or _internal_class_schema(typ, base_schema, typ_frame) File "/home/username/.local/share/virtualenvs/venv_3.10/lib/python3.10/site-packages/marshmallow_dataclass/__init__.py", line 387, in _internal_class_schema raise TypeError( TypeError: IPv4 is not a dataclass and cannot be turned into one.
问题原因
marshmallow_dataclass 8.4.0及后续版本调整了类型解析逻辑,不再支持marshmallow_dataclass.NewType直接携带validate参数的用法。新版本中,该方式生成的IPv4会被识别为普通函数类型,触发将其转为数据类的无效尝试,最终导致报错。这属于依赖包版本迭代中的API行为变更,与Python 3.10本身无关。
解决方法(无需降级)
提供三种可行的替代方案:
方案1:用dataclasses.field搭配metadata指定验证规则
直接在数据类字段中定义验证逻辑,替代原NewType的用法:
from dataclasses import field from marshmallow_dataclass import dataclass, List import marshmallow.validate @dataclass class MyIps: ips: List[str] = field( metadata={ "marshmallow_field": marshmallow.fields.List( marshmallow.fields.String( validate=marshmallow.validate.Regexp(r'^([0-9]{1,3}\.){3}[0-9]{1,3}$') ) ) } ) MyIps.Schema().load({"ips": ["0.0.0.0", "grumble grumble"]})
方案2:自定义marshmallow字段类型
先定义带验证规则的专属字段,再在数据类中引用:
from dataclasses import field from marshmallow_dataclass import dataclass, List import marshmallow import marshmallow.validate # 自定义IPv4验证字段 IPv4Field = marshmallow.fields.String(validate=marshmallow.validate.Regexp(r'^([0-9]{1,3}\.){3}[0-9]{1,3}$')) @dataclass class MyIps: ips: List[str] = field(metadata={"marshmallow_field": marshmallow.fields.List(IPv4Field)}) MyIps.Schema().load({"ips": ["0.0.0.0", "grumble grumble"]})
方案3:结合标准库typing.NewType与字段验证
若需保留类型提示,可使用标准库NewType定义类型,再通过字段验证实现规则:
from typing import NewType from dataclasses import field from marshmallow_dataclass import dataclass, List import marshmallow.validate # 仅用标准库NewType做类型提示 IPv4 = NewType('IPv4', str) @dataclass class MyIps: ips: List[IPv4] = field( metadata={ "marshmallow_field": marshmallow.fields.List( marshmallow.fields.String( validate=marshmallow.validate.Regexp(r'^([0-9]{1,3}\.){3}[0-9]{1,3}$') ) ) } ) MyIps.Schema().load({"ips": ["0.0.0.0", "grumble grumble"]})
内容的提问来源于stack exchange,提问作者myrtille
相关产品推荐
相关产品推荐

