Python中如何对泛型序列参数进行类型收窄,限制为单一子类型?
如何为Python泛型函数添加类型注解,限制序列为单一BaseModel子类实例
问题背景
我需要为一个接收泛型序列的函数添加类型注解,要求序列中的元素必须是BaseModel的**单一子类(或自身)**实例,不能混合多个子类。比如下面的代码里,l1和l2是合法输入,但l3这种混合类型的序列应该触发pyright/mypy的静态检查错误,且不需要运行时校验。同时不能用逐个重载的方案,得支持BaseModel的任意子类。
原代码:
from collections.abc import Sequence from pydantic import BaseModel, ConfigDict class ExampleModel(BaseModel): id: int name: str model_config = ConfigDict(validate_default=True) class NewModel(BaseModel): id: int name: str def func[T: BaseModel](b: Sequence[T]) -> T: return b[0] l1 = [ExampleModel(id=1, name="Test1"), ExampleModel(id=2, name="Test2")] l2 = [NewModel(id=3, name="NewTest1"), NewModel(id=4, name="NewTest2")] l3 = [ExampleModel(id=5, name="Test3"), NewModel(id=6, name="NewTest3")] print(func(l1)) print(func(l2)) print(func(l3))
解决方案
方案1:Python 3.13+ 原生支持(推荐)
Python 3.13给TypeVar新增了constraint参数,可以自定义泛型参数的校验规则。我们可以用这个特性禁止T是联合类型:
from collections.abc import Sequence from typing import TypeVar, Union from pydantic import BaseModel def is_non_union(t) -> bool: # 确保泛型参数不是联合类型 return not isinstance(t, Union) # 定义绑定到BaseModel且非联合类型的泛型变量 T = TypeVar('T', bound=BaseModel, constraint=is_non_union) def func[T](b: Sequence[T]) -> T: return b[0]
这样一来,静态检查工具会识别l3的类型是Sequence[ExampleModel | NewModel],其中T是联合类型,违反约束,直接报错。
方案2:兼容Python 3.12及以下(pyright/mypy专属)
如果用的是Python 3.12或更早版本,可以通过静态检查工具的配置来实现需求:
针对pyright:
- 在项目根目录创建
pyrightconfig.json,开启严格模式:
{ "strict": true, "reportInvalidTypeVarUse": true }
- 用
Annotated标记泛型参数,明确禁止联合类型:
from collections.abc import Sequence from typing import TypeVar, Annotated, Union from pydantic import BaseModel # 指定T必须是BaseModel的非联合子类 T = TypeVar('T', bound=Annotated[BaseModel, {"not": Union}]) def func[T](b: Sequence[T]) -> T: return b[0]
针对mypy:
开启严格模式后,mypy会对混合类型序列的调用给出提示。先在mypy.ini中配置:
[mypy] strict = true disallow_untyped_defs = true
然后保持原泛型函数写法,添加注释明确要求:
from collections.abc import Sequence from typing import TypeVar from pydantic import BaseModel T = TypeVar('T', bound=BaseModel) def func[T](b: Sequence[T]) -> T: """仅接受BaseModel单一子类的序列""" return b[0]
此时传入l3时,mypy会将T推断为联合类型,若要彻底禁止这种情况,可以结合typing_extensions的扩展特性或自定义插件,但一般严格模式下的提示已经满足需求。
效果验证
l1和l2会正常通过静态检查,因为它们的序列是单一子类的集合。l3会触发静态检查错误,提示类型不兼容。
内容的提问来源于stack exchange,提问作者Frostyfeet909
相关产品推荐
相关产品推荐

