我正在尝试将类型 stub 添加到一个库中,该库具有一个名为 List
的集合类,它本质上是内置 list
的包装器。出于所有实际目的,您可以假设它看起来像这样:
# library.py
class List:
def __init__(self, *values):
self.values = values
现在,在我的 stub 文件 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
失败)?
最佳答案
为了了解发生了什么,我认为首先回顾一些背景 Material 会很有帮助。
在 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__
协议(protocol),因此它不知道如何处理 [str]
位!所以你的代码崩溃了。
修复此问题的最简单方法是...
- 修复
library.py
中的类,以便它也扩展Generic[T]
(当您扩展该类时,它会进行一些元编程,以便执行List [str]
有效)。 在
client.py
中切换到使用基于注释的语法——即:def f(): # type: () -> List[str] ...
...因为注释 真正被 Python 运行时完全忽略,现在不需要以任何方式更改
List
类—— stub 对 mypy 来说就足够了.(我们在这里做的是 mypy 将完全忽略
library.py
并且只会查看library.pyi
- 所以,它不关心是否library.py
使List
类通用或不通用。)将
List[str]
写成字符串:def f() -> 'List[str]': ...
Mypy 和其他符合 PEP 484 的类型检查器允许人们在字符串中放置类型提示,作为必要时“前向声明”类型的一种方式,但我们没有理由不能将所有内容都编码为字符串(除了它看起来有点乱)。
我推荐方法 1,因为方法 2 和 3 有点老套和脆弱。
关于python - stub 文件中的 Mypy 泛型类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46980835/