关于必须如何使用 TypeVars,基本上有两个隐含的规则:
- TypeVar 必须在参数类型提示中至少使用一次。
- TypeVar 必须在函数签名中至少出现两次。
如果你不遵循这两条规则,你要么最终构造一个函数类型签名,它实际上并没有做太多事情并且包含一个多余的 TypeVar,要么不可能对其执行类型推断。
您hint_return的函数是违反规则 1 的函数的示例。这是有问题的原因是因为当 mypy 看到如下调用时:
x = hint_return()
...它试图仅x使用调用站点和类型签名中可用的信息来推断 of的类型——它不检查.hint_return
(但是如果 mypy 尝试使用预先存在的类型x作为提示呢?好吧,hint_return实际上不可能在运行时利用该信息,因此该信息不可能与类型推断相关。这又是一个规则 1 的反映: TypeVar 意味着在调用函数时被更具体的类型替换,这意味着您需要实际指定该特定类型作为输入。)
您hint_arg的函数是违反规则 2 的函数示例。在这种情况下,您的 TypeVar 最终没有任何用途:将函数重写为:
def hint_arg_simplified(child: Parent):
pass
毕竟,用传入的实际类型替换 T_co 没有任何意义。由于hint_arg仍然需要能够接受任何 Parent 的任意子类型,因此无论如何它们hint_arg都hint_arg_simplified必须完全一样。
(请记住,如果键入一个函数以接受 Parent,它实际上必须接受 Parent 和 Parent 的任何子类型。即 mypy 假设您的类型遵循 Liskov 替换原则并相应地执行类型检查)
但是做:
T = TypeVar('T', bound=Parent)
def two_args_v1(x: T, y: T) -> None: pass
...与做以下事情有很大不同:
def two_args_v2(x: Parent, y: Parent) -> None: pass
在前者中,我们知道 x 和 y 必须是完全相同的类型,而对于后者我们不知道。这是可以在类型推断期间使用的相关且新的信息。
关于泛型类的澄清说明。从表面上看,他们似乎违反了这些规则。例如,mypy 对下面的类定义非常满意,即使它似乎违反了这两个规则!为什么?
from typing import Generic, TypeVar
T = TypeVar('T')
class Wrapper(Generic[T]):
# Violates rule 2?
def __init__(self, x: T) -> None:
self.x = x
# Violates rule 1 and 2?
def unwrap(self) -> T:
return self.x
嗯,这是因为我们实际上并没有查看完整的类型签名。我们通常会省略 from 的类型self,但它们实际上都还在。一旦我们添加回自动推断的self类型,很明显这两个规则实际上都被遵循了:
class Wrapper(Generic[T]):
def __init__(self: Wrapper[T], x: T) -> None:
self.x = x
def unwrap(self: Wrapper[T]) -> T:
return self.x