You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何定义同时兼具Protocol与Pydantic BaseModel特性的类型?

如何定义同时兼具Protocol与Pydantic BaseModel特性的类型?

嘿,这个问题我熟!你想让一个类型既能像Pydantic BaseModel那样做数据校验和序列化,又能通过Protocol来做结构类型检查对吧?毕竟直接让Protocol继承BaseModel是行不通的,Python不允许这么干,不过咱们有几种靠谱的办法:

方法一:用Protocol定义结构,自动匹配符合条件的BaseModel子类

这种方法最直接,利用Python的结构子类型特性——只要一个BaseModel子类有Protocol要求的属性,就会自动被视为符合该Protocol的类型,Pydantic v2也原生支持这种类型注解的校验。

from typing import Protocol
from pydantic import BaseModel

# 先定义Protocol,明确要求必须有id属性
class HasId(Protocol):
    id: int | str  # 你可以根据实际需求调整id的类型,比如只允许int

# 定义几个符合要求的BaseModel子类
class User(BaseModel):
    id: int
    name: str

class Product(BaseModel):
    id: str
    price: float

# 现在定义你的Foo模型,直接用HasId作为data字段的类型
class Foo(BaseModel):
    data: HasId

# 测试正常场景:符合要求的模型都能传入
user = User(id=123, name="Alice")
foo_with_user = Foo(data=user)
print(foo_with_user)  # 输出: data=User(id=123, name='Alice')

product = Product(id="prod-001", price=29.9)
foo_with_product = Foo(data=product)
print(foo_with_product)  # 输出: data=Product(id='prod-001', price=29.9)

# 测试异常场景:没有id字段的模型会触发校验错误
class InvalidModel(BaseModel):
    name: str

try:
    Foo(data=InvalidModel(name="Bob"))
except Exception as e:
    print(f"错误:{e}")  # 会提示缺少id字段

方法二:结合BaseModel基类与Protocol,强制子类继承

如果你想让所有符合HasId的模型都基于同一个BaseModel基类(比如统一id的校验规则),可以同时定义一个BaseModel基类和对应的Protocol,让子类继承BaseModel基类,这样既能享受BaseModel的特性,又自动符合Protocol的要求。

from typing import Protocol
from pydantic import BaseModel, field_validator

# 定义BaseModel基类,包含id字段和统一的校验规则
class HasIdBase(BaseModel):
    id: str

    @field_validator('id')
    def id_must_not_be_empty(cls, v: str) -> str:
        if not v.strip():
            raise ValueError("id不能为空")
        return v

# 定义对应的Protocol,和基类结构保持一致
class HasId(Protocol):
    id: str

# 子类继承HasIdBase,自动符合HasId Protocol
class Order(HasIdBase):
    order_number: str

# Foo模型依然用HasId作为类型注解
class Foo(BaseModel):
    data: HasId

# 测试:继承HasIdBase的子类可以正常传入
order = Order(id="order-1001", order_number="20240501001")
foo = Foo(data=order)
print(foo)  # 输出: data=Order(id='order-1001', order_number='20240501001')

方法三:添加校验器,确保传入的是BaseModel实例

如果你的需求不仅是要有id属性,还必须是BaseModel的实例(排除那些非BaseModel但有id的普通类),可以给Foo模型的data字段加一个自定义校验器:

from typing import Protocol, Any
from pydantic import BaseModel, field_validator, ValidationError

class HasId(Protocol):
    id: int | str

class Foo(BaseModel):
    data: HasId

    @field_validator('data')
    def ensure_is_basemodel(cls, v: Any) -> Any:
        if not isinstance(v, BaseModel):
            raise ValueError("data必须是Pydantic BaseModel的实例")
        return v

# 测试:非BaseModel类即使有id也会报错
class NonModelWithId:
    def __init__(self, id: int):
        self.id = id

try:
    Foo(data=NonModelWithId(id=789))
except ValidationError as e:
    print(f"错误:{e}")  # 会提示data必须是BaseModel实例

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 12:17:57