Pydantic 2中re正则验证RootModel报错的原因及解决方案咨询
Pydantic 2中使用正则解析字符串的问题解决
1. 报错原因
在Pydantic 2中,类内以单下划线开头的属性会被自动识别为ModelPrivateAttr类型,而非保留原对象实例。你的_FREQUENCY_PATTERN是正则编译后的re.Pattern对象,被Pydantic包装成了ModelPrivateAttr实例,调用其search方法时自然会触发AttributeError——因为ModelPrivateAttr没有这个方法。而Pydantic 1.x不会自动处理这类私有属性,所以原代码能正常运行。
2. 解决方案(兼容Pydantic 2且保留原有功能)
核心思路是避免正则模式被Pydantic包装,同时通过计算字段暴露解析后的三个元素,满足序列化需求。
方案1:用ClassVar标记正则模式,添加计算字段
from pydantic import RootModel, field_validator, computed_field from typing import ClassVar import re class MyClass(RootModel[str]): root: str # 用ClassVar标注,让Pydantic忽略该属性,保留原正则对象 _FREQUENCY_PATTERN: ClassVar[re.Pattern] = re.compile(r"^(\d+)\s*/\s*(\d+)([YMWD])$") @classmethod def _parse(cls, s: str) -> tuple[int, int, str]: match = cls._FREQUENCY_PATTERN.search(s) if match is None: raise ValueError("必须是数字/数字+周期格式(D|W|M|Y),例如:5/1W") n = int(match.group(1)) t = int(match.group(2)) u = match.group(3) return n, t, u @field_validator("root") @classmethod def _check_format(cls, v: str) -> str: cls._parse(v) # 验证输入格式合法性 return v # 定义计算字段,暴露解析后的三个元素,支持序列化 @computed_field(return_type=int) @property def n(self) -> int: return self._parse(self.root)[0] @computed_field(return_type=int) @property def t(self) -> int: return self._parse(self.root)[1] @computed_field(return_type=str) @property def u(self) -> str: return self._parse(self.root)[2]
方案2:初始化时缓存解析结果(优化性能)
如果不想每次访问属性都重复解析字符串,可以在初始化时缓存结果:
from pydantic import RootModel, field_validator, computed_field, PrivateAttr from typing import ClassVar import re class MyClass(RootModel[str]): root: str # 用PrivateAttr存储缓存的解析结果,不参与序列化(如需序列化用computed_field暴露) _n: int = PrivateAttr() _t: int = PrivateAttr() _u: str = PrivateAttr() _FREQUENCY_PATTERN: ClassVar[re.Pattern] = re.compile(r"^(\d+)\s*/\s*(\d+)([YMWD])$") @classmethod def _parse(cls, s: str) -> tuple[int, int, str]: match = cls._FREQUENCY_PATTERN.search(s) if match is None: raise ValueError("必须是数字/数字+周期格式(D|W|M|Y),例如:5/1W") n = int(match.group(1)) t = int(match.group(2)) u = match.group(3) return n, t, u @field_validator("root") @classmethod def _check_format(cls, v: str) -> str: cls._parse(v) return v def __init__(self, **data): super().__init__(**data) # 初始化时解析一次,缓存结果 self._n, self._t, self._u = self._parse(self.root) @computed_field(return_type=int) @property def n(self) -> int: return self._n @computed_field(return_type=int) @property def t(self) -> int: return self._t @computed_field(return_type=str) @property def u(self) -> str: return self._u
说明
ClassVar用于标记类级常量,Pydantic不会将其处理为模型字段或私有属性,确保正则模式保持re.Pattern实例。computed_field用于定义动态计算的属性,默认会包含在模型的序列化结果中,满足你单独访问三个解析元素的需求。- 两种方案都保留了
_parse方法,可直接用于数据存储流程。
内容的提问来源于stack exchange,提问作者Jelkimantis
相关产品推荐
相关产品推荐

