动态创建Pydantic模型时update_forward_refs()报NameError问题
问题描述
通过create_model()动态创建Pydantic模型时,部分场景下调用update_forward_refs()无法解析嵌套在泛型类型内部的前向引用。
可正常运行的静态定义示例
from typing import List, Union from pydantic import BaseModel class Foo(BaseModel): foo: List["Bar"] Bar = Union[Foo, int] Foo.update_forward_refs()
抛出NameError的动态创建示例
逻辑上和上述静态定义等价,但运行时会抛出异常:
from typing import List, Union from pydantic import create_model, BaseModel Foo = create_model("Foo", foo=(List["Bar"], ...)) Bar = Union[Foo, int] Foo.update_forward_refs()
报错信息
Traceback (most recent call last): File "test_forward_ref.py", line 11, in <module> Foo.update_forward_refs() File "pydantic\main.py", line 832, in pydantic.main.BaseModel.update_forward_refs File "pydantic\typing.py", line 382, in pydantic.typing.update_field_forward_refs or class checks. File "pydantic\typing.py", line 62, in pydantic.typing.evaluate_forwardref 'MutableSet', File "C:\Users\Ian\.conda\envs\tso\lib\typing.py", line 518, in _evaluate eval(self.__forward_code__, globalns, localns), File "<string>", line 1, in <module> NameError: name 'Bar' is not defined
触发条件
- 复现环境:Python 3.8、Pydantic 1.8.2
- 字段注解的泛型参数(如
List["Bar"]中的"Bar")使用字符串形式前向引用时触发 - 若字段注解直接为顶层字符串前向引用(如
foo: "Bar")则无异常
问题原因
静态使用class关键字定义模型时,Pydantic会自动将模型绑定到定义所在模块的全局命名空间,update_forward_refs()默认会从该命名空间查找前向引用对应的名称。
通过create_model()动态创建模型时,默认不会自动绑定当前调用位置所在的模块命名空间。Pydantic对字段顶层的字符串前向引用做了特殊的命名空间兜底处理,但嵌套在泛型(如List/Dict)内部的前向引用不会触发该兜底逻辑,解析时找不到后定义的Bar变量,最终抛出NameError。
修复方案
两种方案均可解决该问题,可根据场景选择:
方案1:调用update_forward_refs时显式传入命名空间
解析前向引用时,手动把需要的名称传入方法,强制从指定命名空间查找:
from typing import List, Union from pydantic import create_model Foo = create_model("Foo", foo=(List["Bar"], ...)) Bar = Union[Foo, int] # 方式1:精准传入需要解析的名称 Foo.update_forward_refs(Bar=Bar) # 方式2:直接传入当前作用域所有变量(适合前向引用较多的场景) # Foo.update_forward_refs(**locals(), **globals())
提示:如果在函数内部动态创建模型,推荐使用传入locals()和globals()的方式,避免局部作用域变量无法被识别。
方案2:创建模型时指定所属模块
动态创建模型时通过__module__参数指定当前模块,让模型和静态class定义拥有一致的命名空间绑定行为,后续同模块的其他前向引用不需要额外传参:
from typing import List, Union from pydantic import create_model import sys # 绑定当前模块 current_module_name = sys.modules[__name__].__name__ Foo = create_model("Foo", foo=(List["Bar"], ...), __module__=current_module_name) Bar = Union[Foo, int] Foo.update_forward_refs()
内容的提问来源于stack exchange,提问作者Ian Goldby
相关产品推荐
相关产品推荐

