为了了解发生了什么,我认为首先查看一些背景材料会有所帮助。
在 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 将不再执行其他任何操作。
简而言之,这意味着...
- Python 仍然必须评估每个单独的注解,它必须是一个有效的 Python 表达式
- 但是这样做之后,注释将被忽略。
因此,这意味着当我们使用专门的类型提示时,每个类型提示都会在函数定义时单独评估/必须是有效的表达式,但随后会被 Python 运行时后记忽略。
(作为警告,这种行为在未来可能会略有改变:因为我们必须评估每个注释,使用类型提示确实会带来轻微的性能损失——有一些关于可能改变 Python 的讨论,所以在未来,表达式存储为字符串__annotations__
而不是立即被评估。)
现在,考虑到所有这些,让我们看看你的程序。当 Python 本身运行你的程序时,它会完全忽略你的.pyi
文件。当它遇到:
from library import List
def f() -> List[str]:
return List(*range(10))
...它会首先评估List[str]
然后将结果对象附加到f.__annotations__
.
但是我们遇到了问题!您的List
类型不支持该__getitem__
协议,因此它不知道如何处理该[str]
位!所以你的代码崩溃了。
修复此问题的最简单方法是...
- 修复您的类,
library.py
使其也扩展Generic[T]
(当您扩展该类时,它会进行一些元编程以便List[str]
正常工作)。
切换到使用基于注释的语法client.py
——也就是说,执行以下操作:
def f():
# type: () -> List[str]
...
...由于 Python 运行时真正完全忽略了注释,因此现在无需以List
任何方式更改类 - 存根对于 mypy 来说就足够了。
(我们在这里所做的是 mypy 将完全忽略library.py
并且只会查看library.pyi
- 因此,它不关心是否library.py
使List
类成为通用类。)
写成List[str]
字符串:
def f() -> 'List[str]':
...
Mypy 和其他符合 PEP 484 的类型检查器允许人们将类型提示放入字符串中,作为必要时“前向声明”类型的一种方式,但我们没有理由不能将所有内容都编码为字符串(除了它的外观有点乱)。
我推荐方法 1,因为方法 2 和 3 有点老套和脆弱。