类型提示,其中一个参数是另一个参数的类型

Posted

技术标签:

【中文标题】类型提示,其中一个参数是另一个参数的类型【英文标题】:Type hint where one argument is the type of another 【发布时间】:2020-08-11 18:21:18 【问题描述】:

我在表达一对参数的精确类型提示时遇到了麻烦,其中一个参数需要是某个类型的实例的值,另一个需要是类型本身或某个超类型。

您在 Python 中看到这种情况的一种情况是在上下文管理器(类)的 __exit__ 方法中。

import typing as t
from types import TracebackType
from contextlib import AbstractContextManager

class Acme(AbstractContextManager):
  def __exit__(self, exc_type: t.Type[Exception], exc: Exception,
      tb: Tracebacktype) -> None:
    ...

这种特定情况可能无关紧要,因为上下文管理器由 Python 内部处理,但我仍然想了解如何表达两个参数,其中第一个是第二个的(超)类型。

从概念上讲,我的问题是我想表达exc 的值的类型为exc_type 或某个子类型。在上面的表达式中,我猜 mypy 会非常满意 LookupError, RuntimeError('foo') 这样的参数,即使 RuntimeError 不是 LookupError 的类型。有没有更好的方法来表达这一点,mypy 会发现这种错误?

更新

在这里使用 TypeVars 尝试测试用例:

import typing as t

C = t.TypeVar('C', bound=t.Collection)

def f(t: t.Type[C], c: C) -> None:
  pass

f(t.Dict, [])

我希望 mypy 会抱怨这段代码,因为即使空列表是 Collection 类型,它也不是字典。

【问题讨论】:

我尝试了一段时间,但PEP 通常似乎不鼓励并且有几个issues 支持这一点。这将是一个有用的功能,但它需要Type[some_type_var] 做更多的事情。从哲学上讲,TypeVar 是要走的路。 @modesitt 如果是这样,那就是这样。如果这是一个答案而不是评论,我会接受“不可能这样做”的答案并提供支持证据。 我认为这个问题的正确答案会在一两年内改变。让我们在 2021 年重温。 是的,我希望如此! Python 的类型正在突飞猛进地改进。也就是说,我几乎宁愿看到 mypy 支持抽象方法的推断类型(如 __exit__),这样我就不用担心精确的注释了。 【参考方案1】:

PEP 中的一个部分似乎不鼓励这种Type[some_type_var] 的使用,其中只提供了这种支持的一个子集(特别是用于将TypeVars 绑定到类类型)。看来这种劝阻不是哲学上的(这将是泛型在类型和值都是一流的语言中的常见用法) - 但与类型检查器的实际实现更相关。将TypeVar 用于类方法签名(您问题的一个特例)甚至是late addition to the pep。


不适用于 mypy(截至目前)的可能解决方案

标准TypeVar

Type[some_type_var] 的函数签名中没有类型检查。这不是基于some_type_var 是否为contravariant or covariant 的变量(也不应该是)。

TypeVarType[TypeVar]

T = TypeVar("T")
Meta = TypeVar("Meta", bound=Type[T])

def foo(the_type: Meta, the_value: T):
   ....

TMeta 不能“绑定”在一起。


令我惊讶的是,在某些情况下,您可以排除一些这种行为(但这是一种特殊情况,对我来说似乎未定义)

T = TypeVar('T', str, int, float, bytes)

class Foo:

    def bar(self, the_type: Type[T], the_value: T):
        print(isinstance(the_value, the_type))

f = Foo()
f.bar(str, "baz")
f.bar(str, b"baz")  # wrong!
f.bar(int, 1)
f.bar(int, 3.15159)  # wrong! (but no complaint)
f.bar(float, 1.0)
f.bar(float, 1)  # wrong! (but no complaint)
f.bar(float, b'1.0')  # wrong!

给予

so.py:nn: error: Value of type variable "T" of "bar" of "Foo" cannot be "object"
so.py:nn: error: Value of type variable "T" of "bar" of "Foo" cannot be "object"
Found 2 errors in 1 file (checked 1 source file)

但似乎只适用于 python 的原始类型(这不适用于用户空间类型)(同样,只有 一些 python 的原始类型如图所示(参见 error-miss 与floatint)。我认为这是TypeVar 的一个明确的“不错”扩展(并且使更多Generic 用例成为可能)。


mypy上有一些关于这个的相关问题:

    about class annotation behavior TypeVar of a TypeVar

【讨论】:

【参考方案2】:

这就是TypeVars 的用途!你想要这样的东西:

from types import TracebackType
from typing import Type, TypeVar
from contextlib import AbstractContextManager

_E = TypeVar('_E', bound=Exception)

class Acme(AbstractContextManager):
    def __exit__(
        self,
        exc_type: Type[_E],
        exc: _E,
        tb: Tracebacktype
    ) -> None:
    ...

【讨论】:

嗯,我试过了,但是 mypy 对我明显错误的测试用例非常满意(我将 LookupError 作为 ZeroDivisionError 的类型传入)。我会用我尝试过的方法更新我的问题。

以上是关于类型提示,其中一个参数是另一个参数的类型的主要内容,如果未能解决你的问题,请参考以下文章

laravel中的构造函数依赖注入理解

类型提示:类型类的参数 [重复]

条件参数和返回类型声明(又名类型提示)

如何对返回类型取决于参数的输入类型的函数进行类型提示?

如何使用类型提示为参数指定多种类型? [复制]

如何使用 PHPDoc 对 Callable 的参数进行类型提示?