如何输入从字典中动态检索泛型实例的泛型函数?

问题描述 投票:0回答:1

我有一个泛型类 GenericItem,它有一个名称和一个类型变量,我想将其用作类型提示。

from typing import Generic, TypeVar
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

GenericItems 存储在另一个类中:GenericItemStore,即使用字典来存储 GenericItem 的名称/项目对。它在示例中进行了简化,实际的类存储通用 Endpoint 对象,并在底层具有 httpx 功能来发出 API 请求。

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]

    def some_method_that_need_typehints(self, name: str, actual_instance_of_type: T) -> None:
        generic_item: GenericItem[T] = self.get_from_registry(name)
        # there goes other processing
        return None

当我使用 GenericItemStore's *some_method_that_need_typehints *我想为第二个参数使用 typehint,但因为函数签名是静态定义的,并且实际的通用项是在函数体中(动态)检索的,所以它不会类型提示我想要传递的真实类型。

class BaseModelSubclass(BaseModel):
    some_param: int

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),
    ]
)
# actual_instance_of_type is type hinted by PyCharm as T because it's a static func signature
# I want it to be typehinted as BaseModelSubclass but don't know how and if it's even possible
generic_item_store.some_method_that_need_typehints("First", actual_instance_of_type=...)

有没有办法以某种方式输入这个参数?当前的 python 打字系统是否有可能,或者我只是理解错误? 也许我应该完全改变构建这个应用程序的方式?

我尝试过:

  • 使用自定义通用注册表而不是字典

  • mypy.cast

如有任何帮助,我们将不胜感激。

python generics python-typing
1个回答
1
投票

有没有办法以某种方式输入这个参数?当前的 python 打字系统是否有可能,或者我只是理解错误?也许我应该完全改变构建这个应用程序的方式?

你是对的,这(目前)还不可能。目前,除了

dict
之外,无法定义静态形状的
TypedDict
,并且
TypedDict
只能使用
class D(TypedDict)
/
D = TypedDict('D', ...)
来定义,其中所有键都必须进行硬编码。

除此之外,您也无法像在 TypeScript 中使用

Literal[]
一样派生出由
TypedDict
的所有键组成的
keyof
类型。

如果你真的下定决心,这已经是你能做到的最好的了,但此时你最好在任何地方都使用

typing.cast()

(游乐场链接:MypyPyright

class GenericItem(Generic[T]):
    def __init__(self, name: str) -> None:
        self.name: str = name

class Items(TypedDict):
    First: GenericItem[BaseModelSubclass]
    Second: GenericItem[None]
class GenericItemStore:
    registry: Items

    def __init__(self, items: Items) -> None: ...
    
    @overload
    def method(self, name: Literal['First'], instance: BaseModelSubclass) -> None: ...
    
    @overload
    def method(self, name: Literal['Second'], instance: None) -> None: ...

    def method(self, name: Literal['First', 'Second'], instance: Any) -> None:
        generic_item = self.registry[name]
        reveal_type(generic_item)  # GenericItem[BaseModelSubclass] | GenericItem[None]
generic_item_store = GenericItemStore(
    items = Items(
        First = GenericItem[BaseModelSubclass]('First'),
        Second = GenericItem[None]('Second'),
    )
)

generic_item_store.method("First", BaseModelSubclass())   # fine
generic_item_store.method("First", None)                  # error
generic_item_store.method("Second", None)                 # fine
generic_item_store.method("Second", BaseModelSubclass())  # error
© www.soinside.com 2019 - 2024. All rights reserved.