为什么在 Pycharm 中连接混合类型列表时会收到警告?

Cai*_*Cai 7 python type-hinting pycharm python-3.x

在 Pycharm 中,以下代码会产生警告:

from typing import List

list1: List[int] = [1, 2, 3]
list2: List[str] = ["1", "2", "3"]
list3: List[object] = list1 + list2
#                             ? Expected type List[int] (matched generic type List[_T]),
#                               got List[str] instead.
Run Code Online (Sandbox Code Playgroud)

为什么?我不应该连接两个混合的、暗示类型的列表吗?

Mic*_*x2a 5

根据评论中的要求,以下是类型检查器不允许这样做的一些原因。

第一个原因有点平淡:list.__add__除了包含相同类型的列表之外,类型签名根本不允许传入:

_T = TypeVar('_T')

# ...snip...

class list(MutableSequence[_T], Generic[_T]):

    # ...snip...

    def __add__(self, x: List[_T]) -> List[_T]: ...
Run Code Online (Sandbox Code Playgroud)

支持 PEP 484 的 Pycharm 使用(部分)来自 Typeshed 的数据。

我们有可能以某种方式扩展这种类型签名(例如重载它以在这种情况下也接受 aList[_S]并返回List[Union[_T, _S]]),但我认为没有人愿意调查这种方法的可行性:这种事情不是在实践中太有用了,对于想要严格同类列表或想要对它们进行子类化的人来说,生活变得更加困难,并且可能会破坏依赖当前类型签名的许多现有代码。

这种类型签名也可能反映了在 PEP 484 的初始设计期间做出的更广泛的选择,即假设列表总是同质的——总是包含相同类型的值。

严格来说,PEP 484 的设计者不需要做出这样的选择:他们可以要求类型检查器与它进行特殊情况交互,就像我们目前对元组所做的那样。但我认为,不这样做总体上更简单。(也可以说是更好的风格,但无论如何。)


第二个原因与 PEP 484 类型系统的基本限制有关:无法声明某些函数或方法不会修改状态。

基本上,您想要的行为只有在lst1.__add__(lst2)保证不会改变任一操作数时才是安全的。但是没有办法真正保证这一点——如果lst1某个奇怪的列表子类将项目从lst2自身复制到自身呢?然后暂时放松lst1SomeListSubtype[int]to的类型SomeListSubtype[object]将是不安全的:lst1在从lst2.

当然,实际上编写这样的子类也是一种不好的做法,但是类型检查器不会假设用户在没有强制执行的情况下会遵循最佳实践:类型检查器、编译器和类似工具从根本上说是保守的野兽。


最后,值得注意的是,这些问题都不是本质上不可克服的。类型检查器实现者可以做几件事,例如:

  1. 修改 list 的类型签名(并确保它不会破坏任何现有代码)
  2. 引入某种方式来声明一个方法是纯的——没有变化。基本上,将PEP 591背后的想法概括为也适用于函数。(但这需要编写一个 PEP,修改 typeshed 以使用新的类型结构,做很多仔细的设计和实现工作......)
  3. 当我们确定这两个变量不是列表的子类时,可能是这种交互的特殊情况。(但实际上,我们确定知道这一点的次数非常有限。)

……等等。

但所有这些事情都需要时间和精力去做:这是一个优先级问题。Pycharm(和 mypy 等)的问题跟踪器很长,并且不乏其他需要解决的错误/功能请求。