给定以下 python 代码:
from typing import Protocol, TypeVar
class A:
pass
class B(A):
pass
class C(A):
pass
T = TypeVar("T", B, C, contravariant=True)
class X(Protocol[T]):
def f(self, t: T) -> None:
...
class XTImpl(X[T]):
def f(self, t: T) -> None:
pass
class XBImpl(X[B]):
def f(self, t: B) -> None:
pass
如何键入一个仅接受
XTimpl
实例而不接受
XBImpl
实例的变量?
我真正的用例是我有函数
def fB(x: type[X[B]]):
...
def fC(x: type[X[C]]):
...
def fT(x):
fB(x)
fC(x)
并且我想在 fT
中键入参数 x
X
例如,我想要类似
的东西(为此我收到错误“泛型类的预期类型参数”)。
T = TypeVar("T", B, C, contravariant=True)
如果我们将
T = TypeVar("T", A, contravariant=True)
更改为
那么我们可以只使用 X[A]。 但在我的真实用例中,B 和 C 不是共同祖先的直系子代。
遇到的问题源于类型提示和 Python 中结构类型系统的微妙之处。尽管
XBImpl
在技术上符合
X[T]
的要求(因为它可以处理
B
类型的输入,而
B
是
T
的一个有效选项),但类型检查器无法推断出
fT
中的约束。
解决方法:使用类型界限
为了解决这个问题,我们可以引入另一个类型变量,该变量受限于
B
和
C
的共同基类
A
。这使我们能够表达“接受
X
的任何实例,其中
T
是
A
的子类型”的约束。
from typing import Protocol, TypeVar, Type
class A:
pass
class B(A):
pass
class C(A):
pass
T = TypeVar("T", bound=A, contravariant=True)
class X(Protocol[T]):
def f(self, t: T) -> None:
...
class XTImpl(X[T]):
def f(self, t: T) -> None:
pass
class XBImpl(X[B]):
def f(self, t: B) -> None:
pass
U = TypeVar("U", bound=A)
def fB(x: Type[X[B]]):
...
def fC(x: Type[X[C]]):
...
def fT(x: Type[X[U]]) -> None:
fB(x)
fC(x)
fT(XTImpl) # 正确
fT(XBImpl) # 错误:参数 "x" 应该是 X[<类型变量 U>] 的子类型,与 X[B] 不兼容
在这个修改后的代码中:
-
我们引入了新的类型变量
U
,它受限于A
。 -
fT
的类型提示现在是Type[X[U]]
,这意味着它接受X
的任何类型,其中类型参数是A
的子类型。
这种方法允许类型检查器识别
XBImpl
不满足
fT
的约束,因为它专门用于
B
,而不是任何
A
的子类型。