Pydantic 2中如何让自定义类的模型属性支持多类型输入且无需为每个模型重载方法(仅解决类型检查问题)
Pydantic 2中如何让自定义类的模型属性支持多类型输入且无需为每个模型重载方法(仅解决类型检查问题)
这个问题我太有共鸣了!你想要的就是让自定义类在Pydantic模型里既能兼容多种输入类型(字符串、整数、自定义类本身),又不用给每个模型写一堆重复的重载或者重写__init__方法,还要让Pyright类型检查器乖乖听话对吧?
核心思路
我们只需要在自定义类本身里做手脚:一方面告诉类型检查器,这个类可以接受str/int/自身实例作为输入;另一方面调整Pydantic的核心验证逻辑,保证运行时能正确转换这些输入。所有逻辑都封装在自定义类里,模型只需要直接用这个类型就行,完全不用额外代码。
具体实现方案
1. 给自定义类添加构造函数重载(搞定类型检查器)
通过@overload装饰器给MyCustomInt的__new__方法添加类型提示,告诉Pyright:这个类不仅能通过自身实例化,还能接受str或int作为输入参数。
2. 调整Pydantic核心验证逻辑(保证运行时兼容)
扩展原来的Core Schema,让它支持Python环境下的str输入(原来的逻辑只支持int和自定义类实例),这样运行时传入字符串也能正常转换验证。
修改后的完整可运行代码
from typing import Any, reveal_type, overload from pydantic_core import CoreSchema, core_schema from pydantic import GetCoreSchemaHandler, BaseModel class MyCustomInt(int): # 给类型检查器的重载声明:明确支持的输入类型 @overload def __new__(cls, value: int) -> "MyCustomInt": ... @overload def __new__(cls, value: str) -> "MyCustomInt": ... @overload def __new__(cls, value: "MyCustomInt") -> "MyCustomInt": ... # 实际构造逻辑:整合验证和转换 def __new__(cls, value: int | str | "MyCustomInt") -> "MyCustomInt": # 直接复用已有实例 if isinstance(value, MyCustomInt): return value # 处理字符串转整数 if isinstance(value, str): try: value = int(value) except ValueError: raise ValueError("必须是合法的整数字符串") # 非负验证 if value < 0: raise ValueError("不能是负数") return super().__new__(cls, value) @classmethod def __get_pydantic_core_schema__( cls, _source_type: Any, _handler: GetCoreSchemaHandler, ) -> CoreSchema: # 从int转换的验证逻辑 def validate_from_int(value: int) -> "MyCustomInt": return cls(value) # 从str转换的验证逻辑 def validate_from_str(value: str) -> "MyCustomInt": return cls(value) # 分别定义int和str的验证链 from_int_schema = core_schema.chain_schema( [core_schema.int_schema(), core_schema.no_info_plain_validator_function(validate_from_int)] ) from_str_schema = core_schema.chain_schema( [core_schema.str_schema(), core_schema.no_info_plain_validator_function(validate_from_str)] ) # 最终的核心Schema:区分JSON和Python环境的输入 return core_schema.json_or_python_schema( json_schema=from_int_schema, # JSON环境只接受int类型 python_schema=core_schema.union_schema( [ core_schema.is_instance_schema(cls), # Python环境接受自定义类实例 from_int_schema, # Python环境接受int from_str_schema, # Python环境接受str ] ), serialization=core_schema.plain_serializer_function_ser_schema( str, return_schema=core_schema.str_schema(), when_used='json' ), ) class MyModel(BaseModel): value: MyCustomInt # 直接用自定义类,不用任何额外修饰! # 现在类型检查器完全不报错,运行时也正常 model = MyModel(value="123") reveal_type(model.value) # 输出仍然是 'MyCustomInt' - 完美! model = MyModel(value=123) reveal_type(model.value) # 还是 'MyCustomInt' model = MyModel(value=MyCustomInt(456)) reveal_type(model.value) # 依旧是 'MyCustomInt'
更简化的版本(可选)
如果你觉得Core Schema的代码有点啰嗦,可以直接把验证逻辑全部交给__new__方法,Core Schema只需要委托给自定义类的构造函数就行:
@classmethod def __get_pydantic_core_schema__( cls, _source_type: Any, _handler: GetCoreSchemaHandler, ) -> CoreSchema: # 直接把所有验证转换交给MyCustomInt的构造函数 return core_schema.no_info_plain_validator_function(cls)
这样代码更简洁,类型检查和运行时效果完全一致。
为什么这能解决问题?
- 类型检查器层面:
@overload明确告诉Pyright,MyCustomInt可以接受str/int/自身实例作为输入,所以Pydantic模型的字段类型声明为MyCustomInt时,类型检查器会自动认为这些输入都是合法的。 - 运行时层面:调整后的Core Schema或简化版的委托逻辑,确保Pydantic能正确处理所有输入类型,结合自定义类里的验证逻辑,数据合法性也有保障。
- 无冗余代码:所有逻辑都封装在
MyCustomInt类里,任何模型只要使用这个类型,自动就获得了多类型输入支持,完全不用在模型里写重载或重写__init__。
备注:内容来源于stack exchange,提问作者Paillat
相关产品推荐
相关产品推荐

