为什么 mypy 推断公共基类型而不是所有包含类型的并集?

Jos*_*osh 4 python types type-inference python-3.x mypy

当迭代异构序列(例如,包含T1和类型的元素T2)时,mypy 推断目标变量具有类型(或和object之间共享的另一个基本类型,例如,如果元素是和):T1T2float11.2

xs = [1, "1"]
for x in xs:
    reveal_type(x)  # note: Revealed type is 'builtins.object*'
Run Code Online (Sandbox Code Playgroud)

推断出的类型不是更有意义吗Union[T1, T2]?然后,如果 和 都T1具有T2公共基类所缺少的一些公共属性,则将允许循环体访问该属性,而不会产生令人讨厌的强制转换或 isinstance 断言。

为什么 mypy 推断出单个共享基类型而不是此处Union

Mic*_*x2a 5

选择列表元素的公共基类(选择连接)而不是采用元素的并集是 mypy 精心设计的选择。

简而言之,问题在于,无论您选择两种解决方案中的哪一种,最终都会遇到一些边缘情况,从而给某些人带来不便。例如,在您想要修改添加到列表而不是仅读取列表的情况下,推断并集会很不方便:

class Parent: pass
class Child1(Parent): pass
class Child2(Parent): pass
class Child3(Parent): pass

# If foo is inferred to be type List[Union[Child1, Child2]] instead of List[Parent]
foo = [Child1(), Child2()]

# ...then this will fail with a type error, which is annoying.
foo.append(Child3())
Run Code Online (Sandbox Code Playgroud)

mypy 可能会尝试应用一些聪明的启发式方法来确定是否应该推断连接或并集,但这可能最终会导致最终用户相当混乱且难以预测。

这在实践中也是一个很容易解决的问题——例如,您可以向变量添加显式注释:

from typing import Union, Sized, List

# If you want the union
xs: List[Union[int, str]] = [1, "1"]

# If you want any object with the `__len__` method
ys: List[Sized] = [1, "1"]
Run Code Online (Sandbox Code Playgroud)

因此,考虑到这两个因素,实施一些奇特的启发式方法或完全转向推断联合(并破坏大量现有代码)似乎并不值得。

  • @hoefling - 你很接近 - 类型“List”实际上是“List[Any]”的别名,其中“Any”是动态类型。(mypy 文档在[此处](https://mypy.readthedocs.io/en/latest/kinds_of_types.html#the-any-type)和[此处](https: //mypy.readthedocs.io/en/latest/dynamic_typing.html#dynamic-typing))。然而,您实际上从未在示例中编写过“List”或任何其他类型提示——这种别名是无关紧要的。相反,类型检查器负责选择“xs”应该具有的类型。mypy 通常偏向于推断具体的非动态类型。 (2认同)
  • 值得注意的是,这是一个特定于 mypy 的决定 - PEP 484 实际上并不强制要求任何特定的推理策略,因此类型检查器决定“xs”是类型“List[Any]”同样有效或改为“List[Union[int, str]]”。例如,Facebook 的 [pyre](https://github.com/facebook/pyre-check) 做出了相反的决定:他们偏向于推断联合而不是联接。这是 PEP 484/打字 PEP 的一般模式——它们详细说明特定类型提示的含义,但将这些类型提示的实际推断/使用留给各个类型检查器。 (2认同)