为什么不推断所有静态类型?

Why is all static typing not inferred?

由于 Python 支持类型注释,它支持静态类型规则。在使用 ast 模块生成的 AST 时,令我印象深刻的是,鉴于这样的规则,所有类型都可以推断出来,应该不需要类型注释。给定静态类型编译指示(可能是代码文件顶部的注释),解析器中的附加逻辑层可以遍历 AST 以确定所有变量的类型。

例如,从 Mypy website:

中获取这段代码
d = {}  # type: Dict[str, int]

with open(sys.argv[1]) as f:
    for s in f:
        for word in re.sub('\W', ' ', s).split():
            d[word] = d.get(word, 0) + 1

字典 d 及其键和值是用注释键入的,但类型可以从以下循环中推断出来: s 是一个 str 如果它是在f中,从文件中读取的内容;并且 dict 项值是 int 因为那是赋值表达式 returns.

是不是对代码执行这样的分析对于静态类型的推断来说成本太高了,还是我遗漏了什么?

请注意,此问题与有关动态类型与静态类型或可选类型的讨论无关。我的观点是关于当程序员同意静态类型时的类型推断。

问题是类型注释是可选的。事实上,re 模块没有类型注释,甚至在 Python 3.8 中也没有。当然,分析器可以内省 Python 代码以查看发生了什么。但是,对于某些代码(如 re 模块),代码最终会进入 C-API(在 CPYthon 中)。此时分析器无法确定函数的类型签名是什么。作为人类,我们可以阅读文档并知道 re.sub 总是 return 是 str 的实例,但自动化工具无法知道,除非向它们提供补充类型信息。

那么你会遇到一些函数 return 类型联合的问题。例如。 ** 运算符 (int.__pow__) return 是 intfloatcomplex,具体取决于类型 及其操作数的值。例如

>>> 3 ** 2
9
>>> 3 ** -2
0.1111111111111111
>>> 2 ** 0.5
1.4142135623730951
>>> (-1) ** 0.5
(6.123233995736766e-17+1j) # should really just be 1j

这意味着,给定:

def f(x: int, y: int):
   z = x ** y

z 将被分配 object 的类型(intfloatcomplex 的公共基础),这可能不是什么是需要的。通过给变量一个类型注释,我们可以让 mypy 在将 x ** y 的结果赋值给 z 时进行类型检查,但是以后对 z 的任何操作都可以安全地假定类型z 成为它被定义的样子。