Sun*_*bel 2 python generics stub mypy
我想在类型存根中添加一个名为的集合类List,这实际上是内置函数的包装list。出于所有实际目的,您可以假设它看起来像这样:
# library.py
class List:
def __init__(self, *values):
self.values = values
Run Code Online (Sandbox Code Playgroud)
现在,在我的存根文件library.pyi中:
# library.pyi
from typing import Generic, TypeVar, Iterable
T = TypeVar('T')
class List(Generic[T]):
def __init__(self, *values: T) -> None: ...
Run Code Online (Sandbox Code Playgroud)
如果执行以下操作,我想输入失败:
# client.py
from library import List
def f() -> List[str]:
return List(*range(10))
Run Code Online (Sandbox Code Playgroud)
但是mypy client.py以0退出。此外,python client.py失败以TypeError: 'type' object is not subscriptable。
我的理解是类型提示对运行时没有任何影响。那显然是错误的。有人可以纠正我关于类型提示如何工作的心理模型吗?
而且,有什么可以得到我想要的(即mypy client.py失败)吗?
为了了解正在发生的事情,我认为首先回顾一些背景材料会有所帮助。
在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__)
Run Code Online (Sandbox Code Playgroud)
如果运行此命令,则会得到:
{'x': 23, 'y': [1, 2, 3, 4], 'return': 4}
Run Code Online (Sandbox Code Playgroud)
也就是说,Python会运行3 + 4 * 5,然后[i + 1 for i in range(4)],然后max(3, 4),然后将数据附加到__annotations__。完成此操作后,Python将不执行其他任何操作。
简而言之,这意味着...
因此,这意味着当我们使用专门的类型提示时,每个类型提示在函数定义时都会被单独求值/必须是有效的表达式,但随后会被Python运行时后记忽略。
(请注意,此行为将来可能会稍有变化:由于我们必须评估每个注释,因此使用类型提示确实会带来轻微的性能损失–有人谈论可能会更改Python,因此将来将表达式存储为在字符串__annotations__而不是被立即计算。)
现在,考虑所有这些,让我们看一下您的程序。当Python本身运行程序时,它将完全忽略您的.pyi文件。当遇到:
from library import List
def f() -> List[str]:
return List(*range(10))
Run Code Online (Sandbox Code Playgroud)
...它将首先进行评估,List[str]然后将该结果对象附加到f.__annotations__。
但是我们遇到了问题!您的List类型不支持该__getitem__协议,因此不知道该如何处理[str]!因此,您的代码崩溃了。
修复此问题的最简单方法是...
library.py以便它也可以扩展Generic[T](当您扩展该类时,它会进行一些元编程以便可以List[str]工作)。切换至使用基于注释的语法,即client.py–
def f():
# type: () -> List[str]
...
Run Code Online (Sandbox Code Playgroud)
...由于注释实际上已被Python运行时完全忽略,因此现在无需以List任何方式更改类-存根足以满足mypy的需要。
(我们在这里所做的是mypy将完全忽略library.py并且只会查看library.pyi–因此,它并不关心是否library.py使List该类通用。)
写List[str]为字符串:
def f() -> 'List[str]':
...
Run Code Online (Sandbox Code Playgroud)
Mypy和其他符合PEP 484的类型检查器允许人们根据需要在类型中将类型提示作为“向前声明”类型的方式,但是没有理由我们不能仅将所有内容编码为字符串(除了看起来如何)有点混乱)。
我建议使用方法1,因为方法2和3有点笨拙和脆弱。