类型提示,其中一个参数是另一个参数的类型
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]
的使用,其中只提供了这种支持的一个子集(特别是用于将TypeVar
s 绑定到类类型)。看来这种劝阻不是哲学上的(这将是泛型在类型和值都是一流的语言中的常见用法) - 但与类型检查器的实际实现更相关。将TypeVar
用于类方法签名(您问题的一个特例)甚至是late addition to the pep。
不适用于 mypy(截至目前)的可能解决方案:
标准TypeVar
Type[some_type_var]
的函数签名中没有类型检查。这不是基于some_type_var
是否为contravariant
or covariant
的变量(也不应该是)。
TypeVar
的 Type[TypeVar]
T = TypeVar("T")
Meta = TypeVar("Meta", bound=Type[T])
def foo(the_type: Meta, the_value: T):
....
T
和 Meta
不能“绑定”在一起。
令我惊讶的是,在某些情况下,您可以排除一些这种行为(但这是一种特殊情况,对我来说似乎未定义)
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 与float
和int
)。我认为这是TypeVar
的一个明确的“不错”扩展(并且使更多Generic
用例成为可能)。
mypy上有一些关于这个的相关问题:
-
about class annotation behavior
TypeVar
of a TypeVar
【讨论】:
【参考方案2】:这就是TypeVar
s 的用途!你想要这样的东西:
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 的类型传入)。我会用我尝试过的方法更新我的问题。以上是关于类型提示,其中一个参数是另一个参数的类型的主要内容,如果未能解决你的问题,请参考以下文章