假设我有课
Number
:
from typing import TypeVar, Generic
T = TypeVar('T')
class Number(Generic[T]):
value: T
其中
T
是类型变量,可以是int
、float
、Decimal
、Fraction
等
现在我想定义一些方法,比如
__add__
。简单来说,我们可以将参数和返回值设置为与值相同的类型,例如:
class Number(Generic[T]):
value: T
def __add__(self, other: T) -> T: ...
但是,当像
int + float
这样的东西是可添加的,但上面的类型提示不起作用时。
问题是:类型提示系统如何获取
T
的信息,告知只有某些类型(不仅是T
,甚至T
不能)可以与T
类型进行操作
,比如:
class Number(Generic[T]):
value: T
def __add__(self, other: T_addable) -> T_addition_result: ...
我尝试使用
Protocol
、covariant
、contravariant
from typing import TypeVar, Protocol, runtime_checkable
T = TypeVar("T")
T_co = TypeVar("T_co", covariant=True)
T_contra = TypeVar("T_contra", contravariant=True)
@runtime_checkable
class SupportsAdd(Protocol[T_contra, T_co]):
__slots__ = ()
def __add__(self, x: T_contra) -> T_co: ...
但这不能从
T_contra
确定 T_co
和 T
。通常这只能用作SupportsAdd[Any, Any]
,没有什么帮助。
第一个问题是
T
未绑定。它可以是任何东西,你可以构造一个 Number[str]
或 Number[list[set[int]] | dict[tuple[int, int], str]]
...
有两种(三种,如果算上像
AnyStr
这样的约束类型变量)方法来约束它:
numbers.Number
。这就是我开始的地方:
T = TypeVar('T')
class SupportsAdd(Protocol[T]):
def __add__(self, other: T) -> T: ...
class Number(Generic[T]):
value: SupportsAdd[T]
def __init__(self, value: SupportsAdd[T]) -> None:
self.value = value
def __add__(self, other: T) -> T:
return self.value + other
现在,这几乎起作用了,除了我需要明确表示
T
:
n = Number[float](3)
reveal_type(n + 1.0) # float
...并且它并不总是给出所需的答案:
reveal_type(n + 1) # float, runtime type: int
好吧,也许我们可以将
T
分成协变和逆变部分,正如您所写。这对我来说并不成功。我能想到的其他事情也没有。
就在那时我决定退后一步。这里复杂性的根源是什么?
关于数字类型的事情是,它们是由 mypy 等工具进行特殊处理的。
像
__add__
这样的运算符只能输入为(Self, Self) -> Self
,混合和匹配数字类型的能力是通过“int
可分配给float
”、“float
可分配给complex
”等规则来实现的。
”,等等。因此,如果您这样做 3 + 4.2
,mypy 会将其视为 3
被分配给一个浮点数,然后拥有 float + float
,而不是 int + float
。
所以问题是你的
Number
类同样不是特殊情况:一般情况下你不能将 Number[int]
分配给 Number[float]
。基本上,您不能构造一个类定义,使得对于一个实例 n
assert_type(n + 1.0, float)
和 assert_type(n + 1, int)
都通过。
我很想被证明是错的,但我看不到任何解决办法。