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

Python 为什么mypy会推断公共基类型而不是所有包含类型的并集?,python,python-3.x,types,type-inference,mypy,Python,Python 3.x,Types,Type Inference,Mypy,当在异类序列上迭代时(例如,包含类型为T1和T2的元素),mypy推断目标变量具有类型为的object(或者T1和T2之间共享的另一个基本类型,例如,如果元素为1和1.2),则为浮点: xs=[1,“1”] 对于xs中的x: 显示类型(x)#注:显示类型为'builtins.object*' 如果推断的类型是Union[T1,T2],不是更有意义吗?然后,如果T1和T2都具有公共基类所缺少的某些公共属性,那么将允许循环体访问该属性,而不会导致强制类型转换或isinstance断言 为什么myp

当在异类序列上迭代时(例如,包含类型为
T1
T2
的元素),mypy推断目标变量具有类型为
的object
(或者
T1
T2
之间共享的另一个基本类型,例如,如果元素为
1
1.2
),则为
浮点

xs=[1,“1”]
对于xs中的x:
显示类型(x)#注:显示类型为'builtins.object*'
如果推断的类型是
Union[T1,T2]
,不是更有意义吗?然后,如果
T1
T2
都具有公共基类所缺少的某些公共属性,那么将允许循环体访问该属性,而不会导致强制类型转换或isinstance断言


为什么mypy在这里推断一个单一的共享基类而不是一个
并集

选择列表元素的公共基类(选择连接)而不是元素的并集是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())
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"]

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

像往常一样,这是一个很好的答案。我一直认为这种行为类似于Java的泛型,例如,非类型化的
List
List
的别名,让我吃惊的是
mypy
选择了公共祖先类,谢谢你指出。@hoefling--你很接近了--
List
类型实际上是
List[Any]
的别名,其中
Any
是动态类型。(mypy文档中有关于
Any
vs
object
和的更多信息)。然而,您实际上从未在示例中编写过
List
或任何其他类型的提示——这种别名是不相关的。相反,类型检查器负责选择
xs
应该具有的类型。mypy通常偏向于推断具体的、非动态的类型。但值得注意的是,这是一个mypy特定的决定——PEP 484实际上并不强制要求任何特定的推断策略,因此类型检查器决定
xs
是类型
List[any]
还是
List[Union[int,str]
同样有效。例如,Facebook做出了相反的决定:他们倾向于推断工会而不是加入。这是PEP 484/键入PEP的一般模式——它们详细说明了特定类型提示的含义,但将这些类型提示的实际推断/使用留给各个类型检查器。