自定义ListAllIfEmpty类在Pydantic中使用的Schema生成问题求助
我需要实现一个自定义列表类ListAllIfEmpty,继承自Python内置list,仅重写__contains__方法:当列表为空时,判定其包含任意元素;非空时与普通列表行为一致。希望将该类用于Pydantic的BaseModel中,同时保留Pydantic对普通列表的所有校验能力。
当前实现代码:
class ListAllIfEmpty(list): """ Normal list. If the list is empty we will consider it contains everything """ def __contains__(self, item): if not self: return True else: return super().__contains__(item)
在Pydantic模型中的使用方式:
class A(BaseModel): value1: str value2: ListAllIfEmpty[int] = Field(default_factory=lambda: [])
运行时出现Schema生成错误:
pydantic.errors.PydanticSchemaGenerationError: Unable to generate pydantic-core schema for [...].ListAllIfEmpty[typing.Annotated[datetime.date, BeforeValidator(func=<function validate_date at 0x000001EB701040E0>), PlainSerializer(func=<function format_date at 0x000001EB701053A0>, return_type=PydanticUndefined, when_used='unless-none')]]. Set
arbitrary_types_allowed=Truein the model_config to ignore this error or implement__get_pydantic_core_schema__on your type to fully support it.If you got this error by calling handler(<some type>) within
__get_pydantic_core_schema__then you likely need to callhandler.generate_schema(<some type>)since we do not call__get_pydantic_core_schema__on<some type>otherwise to avoid infinite recursion.
设置arbitrary_types_allowed=True会丢失Pydantic的校验能力,希望保持类像普通列表一样的实例化方式(如a = [1,2,3]),寻求解决方案。
要让Pydantic正确识别自定义列表类型并保留校验能力,需要为ListAllIfEmpty实现__get_pydantic_core_schema__类方法,同时让类支持泛型(因为用到了ListAllIfEmpty[int]这种形式)。
完整实现代码
from typing import Any, TypeVar, Generic from pydantic_core import core_schema from pydantic import BaseModel, Field T = TypeVar('T') class ListAllIfEmpty(list, Generic[T]): """ 普通列表,但为空时会判定包含任意元素 """ def __contains__(self, item: Any) -> bool: if not self: return True return super().__contains__(item) @classmethod def __get_pydantic_core_schema__( cls, source_type: Any, handler: core_schema.SchemaGenerator ) -> core_schema.CoreSchema: # 获取对应泛型列表的Pydantic核心Schema list_schema = handler.generate_schema(list[T]) # 自定义序列化/反序列化逻辑,确保实例化为ListAllIfEmpty return core_schema.no_info_wrap_validator_function( lambda v, info: cls(v), list_schema, serialization=core_schema.plain_serializer_function_ser_schema( lambda instance: list(instance), return_type=list ), )
在Pydantic模型中使用
class A(BaseModel): value1: str value2: ListAllIfEmpty[int] = Field(default_factory=ListAllIfEmpty)
关键说明
- 泛型支持:继承
Generic[T]后,ListAllIfEmpty可以像标准List[T]一样指定元素类型,Pydantic会自动对元素执行类型校验。 - Schema兼容:
__get_pydantic_core_schema__方法中,先通过handler.generate_schema(list[T])获取普通列表的校验规则,再将校验后的结果包装为ListAllIfEmpty实例,既保留原生列表的校验能力,又实现自定义类的逻辑。 - 序列化处理:通过
plain_serializer_function_ser_schema确保序列化时将自定义列表转换为普通列表,避免序列化异常。 - 实例化方式:依然支持类似普通列表的实例化,比如
empty_list = ListAllIfEmpty()、num_list = ListAllIfEmpty([1,2,3]),也可以在Pydantic中用default_factory=ListAllIfEmpty设置默认值。
验证测试
# 测试空列表的contains逻辑 empty_list = ListAllIfEmpty[int]() assert 5 in empty_list assert "test_str" in empty_list # 测试非空列表的contains逻辑 non_empty_list = ListAllIfEmpty[int]([1,2,3]) assert 2 in non_empty_list assert 4 not in non_empty_list # 测试Pydantic自动校验与类型转换 model = A(value1="demo", value2=["1", 2, 3]) assert model.value2 == ListAllIfEmpty([1,2,3]) # 非法输入会触发校验错误 try: A(value1="demo", value2=["not_an_integer"]) except Exception as e: print(e) # 抛出类型校验错误,符合预期
内容的提问来源于stack exchange,提问作者Ana Arduengo

