如何为从字典动态获取泛型实例的泛型函数添加类型提示?
问题解答
是否能实现该类型提示?当前Python类型系统是否支持?
Python的静态类型检查器(如mypy、Pyright)本身不支持基于运行时字符串参数动态推导类型,但可以通过显式建模名称与类型的映射关系实现类似的静态类型提示效果。这需要提前确定所有可能的名称对应的类型,或者通过泛型系统绑定名称到类型的映射。
解决方案:调整应用结构
方案1:使用函数重载(适用于固定已知名称的场景)
如果GenericItemStore实例的项是固定的(比如示例中的"First"和"Second"),可以通过@overload装饰器为目标方法定义多个签名,每个签名对应一个名称和其关联的类型:
from typing import Generic, TypeVar, overload, Literal from pydantic import BaseModel T = TypeVar("T", bound=BaseModel) class GenericItem(Generic[T]): def __init__(self, name: str, type_that_i_need_in_typehint: type[T] | None) -> None: self.name: str = name self.type_: type[T] | None = type_that_i_need_in_typehint class BaseModelSubclass(BaseModel): some_param: int class GenericItemStore: def __init__(self, items: list[GenericItem[T]]) -> None: self.generic_items_registry: dict[str, GenericItem[T]] = {} self._configure_registry(items) def _configure_registry(self, items: list[GenericItem[T]]) -> None: for item in items: self.generic_items_registry[item.name] = item def get_from_registry(self, name: str) -> GenericItem[T]: return self.generic_items_registry[name] @overload def some_method_that_need_typehints(self, name: Literal["First"], actual_instance_of_type: BaseModelSubclass) -> None: ... @overload def some_method_that_need_typehints(self, name: Literal["Second"], actual_instance_of_type: None) -> None: ... def some_method_that_need_typehints(self, name: str, actual_instance_of_type: T | None) -> None: generic_item = self.get_from_registry(name) # 处理逻辑 return None # 使用示例 generic_item_store = GenericItemStore( items=[ GenericItem(name="First", type_that_i_need_in_typehint=BaseModelSubclass), GenericItem(name="Second", type_that_i_need_in_typehint=None), ] ) # 传入"First"时,类型检查器会提示参数应为BaseModelSubclass generic_item_store.some_method_that_need_typehints("First", actual_instance_of_type=BaseModelSubclass(some_param=1)) # 传入"Second"时,会提示参数可为None generic_item_store.some_method_that_need_typehints("Second", actual_instance_of_type=None)
方案2:泛型化Store的名称-类型映射(更灵活的场景)
如果项在创建Store时就确定,但数量较多或需要动态扩展,可以让GenericItemStore泛型化一个名称到类型的映射(使用TypedDict定义映射规则):
from typing import Generic, TypeVar, TypedDict, cast, keyof from pydantic import BaseModel T = TypeVar("T", bound=BaseModel) StoreMapping = TypeVar("StoreMapping", bound=TypedDict) class GenericItem(Generic[T]): def __init__(self, name: str, type_that_i_need_in_typehint: type[T] | None) -> None: self.name: str = name self.type_: type[T] | None = type_that_i_need_in_typehint class BaseModelSubclass(BaseModel): some_param: int class AnotherModel(BaseModel): another_param: str # 定义当前Store的名称-类型映射规则 class MyStoreMapping(TypedDict): First: type[BaseModelSubclass] Second: type[AnotherModel] class GenericItemStore(Generic[StoreMapping]): def __init__(self, items: list[GenericItem[T]]): self.generic_items_registry: dict[str, GenericItem[T]] = {} self._configure_registry(items) def _configure_registry(self, items: list[GenericItem[T]]) -> None: for item in items: self.generic_items_registry[item.name] = item def get_from_registry(self, name: keyof[StoreMapping]) -> GenericItem[StoreMapping[name]]: return cast(GenericItem[StoreMapping[name]], self.generic_items_registry[name]) def some_method_that_need_typehints(self, name: keyof[StoreMapping], actual_instance_of_type: StoreMapping[name]) -> None: generic_item = self.get_from_registry(name) # 处理逻辑 return None # 使用示例 generic_item_store = GenericItemStore[MyStoreMapping]( items=[ GenericItem(name="First", type_that_i_need_in_typehint=BaseModelSubclass), GenericItem(name="Second", type_that_i_need_in_typehint=AnotherModel), ] ) # 传入"First"时,自动提示参数类型为BaseModelSubclass generic_item_store.some_method_that_need_typehints("First", actual_instance_of_type=BaseModelSubclass(some_param=1)) # 传入"Second"时,自动提示参数类型为AnotherModel generic_item_store.some_method_that_need_typehints("Second", actual_instance_of_type=AnotherModel(another_param="test"))
注:keyof是Python 3.11+ typing模块新增特性,低版本可通过typing_extensions.keyof引入。
关键限制
- 所有方案都要求名称与类型的映射在静态类型检查时是已知的,如果Store在运行时动态添加项,静态类型检查器无法推断新增项的类型提示。
- 需要确保类型检查器开启严格模式(如mypy的
--strict参数),才能正确识别泛型和重载定义。
内容的提问来源于stack exchange,提问作者Slava Pasedko
相关产品推荐
相关产品推荐

