如何正确键入 Python 方法返回带有通用类型实例及其通用类型 属性 的字典?
How to properly type Python methods returning a dict with a generically typed instance and its generically typed property?
我有以下 Python 函数并希望以 mypy 理解的方式输入它
return 类型作为字典,项目的类型作为值类型,id 类型作为键类型:
def id_map(items):
return {item.id: item for item in items}
当前的方法如下所示:
from typing import Iterable, Protocol, TypeVar
S = TypeVar("S", covariant=True, bound="ClassWithId")
T = TypeVar("T", covariant=True)
class ClassWithId(Protocol[S, T]):
def __new__(cls: type[S], *args: Any, **kwargs: Any) -> "ClassWithId[S, T]":
...
@property
def id(self: S) -> T:
...
def id_map(items: Iterable[ClassWithId[S, T]]) -> dict[T, S]:
return {item.id: cast(S, item) for item in items}
这当前导致类型 builtins.dict[Any, Any]
。我想不出一个不同的实现方式来实现它。
我想知道使用 mypy 是否完全可行。
TLDR:删除自身类型变量 S
。它不是必需的,但会导致类型检查器不太可能理解的类型关系。
class ClassWithId(Protocol[T]):
@property
def id(self) -> T:
...
类型变量可以以两种方式使用:作为调用中的推断变量或作为泛型类型中的声明变量。前者用于受使用上下文约束的类型,后者用于必须在整个 class.
中保持一致的类型
S
类型的 var 是 Self
类型;这总是可以从用法中推断出来,不需要在类型本身中重复。否则,您构造的递归类型可能会混淆当前的类型检查器。
从 generic/protocol 中删除 S
表明它实际上并未用于任何类型关系: T
完全受 class [=19= 约束].无需参考S
来定义输入和输出之间的任何关系。
class ClassWithId(Protocol[T]):
@property
def id(self: S) -> T: ...
# ^ defined by generic class, not S
注意 id_map
也不依赖于 S
——知道 T
或 ClassWithId[T]
中的任何一个就足以了解另一个。因此,可以完全删除 S
。
T = TypeVar("T", covariant=True)
class ClassWithId(Protocol[T]):
@property
def id(self) -> T:
...
def id_map(items: Iterable[ClassWithId[T]]) -> dict[T, ClassWithId[T]]:
return {item.id: item for item in items}
如果 id_map
应该保留 确切的 ClassWithId[T]
而不仅仅是协议,这对应于 更高级的类型。原则上,这由具有协议范围的类型变量表示。
CWI = TypeVar("CWI", bound=ClassWithId)
def id_map(items: Iterable[CWI[T]]) -> dict[T, CWI[T]]:
return {item.id: item for item in items}
然而,MyPy does not supported higher kinded types at the moment.
我有以下 Python 函数并希望以 mypy 理解的方式输入它 return 类型作为字典,项目的类型作为值类型,id 类型作为键类型:
def id_map(items):
return {item.id: item for item in items}
当前的方法如下所示:
from typing import Iterable, Protocol, TypeVar
S = TypeVar("S", covariant=True, bound="ClassWithId")
T = TypeVar("T", covariant=True)
class ClassWithId(Protocol[S, T]):
def __new__(cls: type[S], *args: Any, **kwargs: Any) -> "ClassWithId[S, T]":
...
@property
def id(self: S) -> T:
...
def id_map(items: Iterable[ClassWithId[S, T]]) -> dict[T, S]:
return {item.id: cast(S, item) for item in items}
这当前导致类型 builtins.dict[Any, Any]
。我想不出一个不同的实现方式来实现它。
我想知道使用 mypy 是否完全可行。
TLDR:删除自身类型变量 S
。它不是必需的,但会导致类型检查器不太可能理解的类型关系。
class ClassWithId(Protocol[T]):
@property
def id(self) -> T:
...
类型变量可以以两种方式使用:作为调用中的推断变量或作为泛型类型中的声明变量。前者用于受使用上下文约束的类型,后者用于必须在整个 class.
中保持一致的类型S
类型的 var 是 Self
类型;这总是可以从用法中推断出来,不需要在类型本身中重复。否则,您构造的递归类型可能会混淆当前的类型检查器。
从 generic/protocol 中删除 S
表明它实际上并未用于任何类型关系: T
完全受 class [=19= 约束].无需参考S
来定义输入和输出之间的任何关系。
class ClassWithId(Protocol[T]):
@property
def id(self: S) -> T: ...
# ^ defined by generic class, not S
注意 id_map
也不依赖于 S
——知道 T
或 ClassWithId[T]
中的任何一个就足以了解另一个。因此,可以完全删除 S
。
T = TypeVar("T", covariant=True)
class ClassWithId(Protocol[T]):
@property
def id(self) -> T:
...
def id_map(items: Iterable[ClassWithId[T]]) -> dict[T, ClassWithId[T]]:
return {item.id: item for item in items}
如果 id_map
应该保留 确切的 ClassWithId[T]
而不仅仅是协议,这对应于 更高级的类型。原则上,这由具有协议范围的类型变量表示。
CWI = TypeVar("CWI", bound=ClassWithId)
def id_map(items: Iterable[CWI[T]]) -> dict[T, CWI[T]]:
return {item.id: item for item in items}
然而,MyPy does not supported higher kinded types at the moment.