在存根文件Mypy通用类

问题描述 投票:2回答:2

我玩弄与周围添加类型存根到具有所谓List一个集合类,这基本上是围绕着内置list的包装库。对于所有的实际目的,你可以假设它看起来像这样:

# library.py
class List:
    def __init__(self, *values):
        self.values = values

现在,在我的存根文件library.pyi我有:

# library.pyi
from typing import Generic, TypeVar, Iterable
T = TypeVar('T')
class List(Generic[T]):
   def __init__(self, *values: T) -> None: ...

我想打字,如果我这样做会失败:

# client.py
from library import List
def f() -> List[str]:
    return List(*range(10))

然而mypy client.py以0退出此外,该python client.py失败TypeError: 'type' object is not subscriptable

我的理解是,类型提示并没有对运行造成任何影响。那显然是错误的。有人可以纠正我的类型提示是如何工作的心智模式?

此外,有没有得到我想要的(即有mypy client.py失败)?

python generics stub mypy
2个回答
4
投票

为了了解发生了什么事情,我觉得这是有帮助的先回顾一些背景材料。

在Python 3.0,Python就添加了被称为功能注释的新的语言特性。功能注释,由自己,什么都没有做与类型的注释 - 他们是一个简单的方法来连接到任意功能的信息。

基本上,Python做是它把你包括任何注解,评估它们,然后将其添加该功能的__annotations__场。例如,尝试运行下面的代码:

def foo(x: 3 + 4 * 5, y: [i + 1 for i in range(4)]) -> max(3, 4):
    pass

print(foo.__annotations__)

如果我们运行这个,我们会得到:

{'x': 23, 'y': [1, 2, 3, 4], 'return': 4}

也就是说,Python会运行3 + 4 * 5,然后[i + 1 for i in range(4)],然后max(3, 4),然后附上数据__annotations__。它完成这样做之后,Python将别的什么也不做。

简而言之,这意味着...

  1. 蟒蛇仍必须评估每一个单独的注释,它必须是一个有效的Python表达式
  2. 但这样做之后,忽略注释。

因此,这意味着,当我们使用专门类型提示,每种类型的提示进行单独评估/必须在函数的定义时一个有效的表达式,但随后由Python运行后记忽略。

(作为一个警告,这种行为可能会稍微改变未来:使用类型提示并引入轻微的性能损失,因为我们必须评估每一个注释, - 有大约在未来可能改变的Python等等一些会谈,表达被存储为在__annotations__字符串,而随后被立即计算。)


现在,考虑所有的考虑,让我们看一下你的程序。当Python本身运行您的程序,它会完全忽略你的.pyi文件。当它遇到:

from library import List

def f() -> List[str]:
    return List(*range(10))

...它会首先评估List[str]然后附上导致对象f.__annotations__

但是,我们碰到一个问题!您List类型不支持__getitem__协议,所以它不知道如何处理[str]位呢!所以,你的代码崩溃。

最简单的方法来修复这个是要么...

  1. 修复您在library.py类,因此它也扩展Generic[T](当你扩展这个类,它的一些元编程,这样做List[str]工作)。
  2. 切换到client.py使用基于注释语法 - 那就是,这样做: def f(): # type: () -> List[str] ... ......因为意见真正完全由Python运行时会忽略,现在有没有必要改变以任何方式List类 - 存根将足以mypy。 (我们在这里做的是mypy会完全忽略library.py,只会看library.pyi - 所以,如果library.py使得List类通用与否也就没有在意。)
  3. List[str]作为一个字符串: def f() -> 'List[str]': ... Mypy,和其他PEP 484兼容的类型检查可以让人们把类型提示的字符串为“向前声明”作为必要类型的一种方式,但没有理由为什么我们不能仅仅从它的外观编码一切作为一个字符串(除有些凌乱)。

我建议的方法1,因为接近2和3是有点哈克和脆弱。


1
投票

失败的全部原因是因为打字存根对运行没有影响。

def f() -> List[str]的返回注释在运行时计算。这未按图书馆的List不从Generic[T]继承,所以List[str]抛出一个错误。

此外,对于可变参数的参数注释应该是每个参数的类型。即,*values: Iterable[T]意味着每个项目应类型的迭代。你大概意思*values: T

至于失败的原因,它可能只是在mypy的错误。尝试使对于一__new__代替typestub作为__new__确定对象的类型。例如:

class List(Generic[T]):
    def __new__(cls, *values: T) -> List[T]: ...
    def __init__(self, *values: T) -> None: ...

推荐问答