以更棘手的方式进行泛型的 Python 类型提示

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

假设我有课

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]
,没有什么帮助。

python generics type-hinting covariant
1个回答
0
投票

第一个问题是

T
未绑定。它可以是任何东西,你可以构造一个
Number[str]
Number[list[set[int]] | dict[tuple[int, int], str]]
...

有两种(三种,如果算上像

AnyStr
这样的约束类型变量)方法来约束它:

  1. 将类型变量绑定到最派生的超类型。遗憾的是,mypy 不支持所有数字类型的超类型
    numbers.Number
  2. 利用已有的协议。

这就是我开始的地方:

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)
都通过。

我很想被证明是错的,但我看不到任何解决办法。

© www.soinside.com 2019 - 2024. All rights reserved.