TypeVar注释参数与返回值

时间:2020-03-27 14:43:01

标签: python python-3.x type-hinting mypy

考虑一个父类和从该父类继承的多个子级的情况。设置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进行退货类型提示的正确方法是什么?

1 个答案:

答案 0 :(得分:2)

关于必须如何使用TypeVars,基本上有两个隐式规则:

  1. 在您的参数类型提示中必须至少使用一次TypeVar。
  2. TypeVar必须在函数签名中至少出现两次。

如果您不遵循这两个规则,那么您要么会构建一个功能类型签名,但实际上并不会做太多事情,并且包含多余的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_arghint_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