考虑一个父类和从该父类继承的多个子级的情况。设置TypeVar
的类型,以在孩子通过或返回时提示孩子。为了简单和说明,只创建了一个孩子。
from typing import TypeVar
class Parent(object):
pass
class Child(Parent):
pass
T_co = TypeVar("T_co", bound=Parent)
mypy
的响应方式会有所不同,具体取决于应用类型提示的位置。当适用于退货时,mypy
将引发以下错误
def hint_return() -> T_co:
return Child()
Incompatible return value type (got "Child", expected "T_co")mypy(error)
但是mypy
提出论点时没有提出投诉。
def hint_arg(child: T_co):
pass
register_arg(Child())
为什么会出现这种差异?以及使用TypeVar
进行退货类型提示的正确方法是什么?
答案 0 :(得分:2)
关于必须如何使用TypeVars,基本上有两个隐式规则:
如果您不遵循这两个规则,那么您要么会构建一个功能类型签名,但实际上并不会做太多事情,并且包含多余的TypeVar,或者无法执行类型推断。
您的hint_return
函数是违反规则1的函数的示例。之所以有问题,是因为mypy看到如下调用:
x = hint_return()
...它尝试使用 just 推断调用站点和类型签名中可用的信息x
的类型-它不检查主体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
好吧,这是因为我们实际上不是在查看完整的类型签名。我们通常会省略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