python - stub 文件中的 Mypy 泛型类

标签 python generics stub mypy

我正在尝试将类型 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 将不会执行任何其他操作。

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

  1. Python 仍必须评估每个单独的注释,它必须是有效的 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__ 协议(protocol),因此它不知道如何处理 [str] 位!所以你的代码崩溃了。

修复此问题的最简单方法是...

  1. 修复 library.py 中的类,以便它也扩展 Generic[T](当您扩展该类时,它会进行一些元编程,以便执行 List [str] 有效)。
  2. client.py 中切换到使用基于注释的语法——即:

    def f():
        # type: () -> List[str]
        ...
    

    ...因为注释 真正被 Python 运行时完全忽略,现在不需要以任何方式更改 List 类—— stub 对 mypy 来说就足够了.

    (我们在这里做的是 mypy 将完全忽略 library.py 并且只会查看 library.pyi - 所以,它不关心是否library.py 使 List 类通用或不通用。)

  3. List[str] 写成字符串:

    def f() -> 'List[str]':
        ...
    

    Mypy 和其他符合 PEP 484 的类型检查器允许人们在字符串中放置类型提示,作为必要时“前向声明”类型的一种方式,但我们没有理由不能将所有内容都编码为字符串(除了它看起来有点乱)。

我推荐方法 1,因为方法 2 和 3 有点老套和脆弱。

关于python - stub 文件中的 Mypy 泛型类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46980835/

相关文章:

rspec - Controller 中的 stub super 方法

java - Android stub 问题

ruby - 如何使用 Fog::Compute 对象的 Rspec any_instance stub ?

Python - Pandas - 'normal' 列的唯一约束

python - wait_for_message 可以检查多个内容吗?

python - 将 Flask 开发服务器配置为在整个网络中可见

python - 如何使用 webEngineContextLog 禁用 QWebEngineView 日志记录?

可比较类型的 C# 泛型

java - @SafeVarargs 接口(interface)方法

c# - Method.Invoke 因参数计数不匹配而失败