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

问题描述 投票:0回答:3

在 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.

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

python pycharm python-typing
3个回答
7
投票

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

第一个原因有点平淡:

list.__add__
的类型签名根本不允许传入包含相同类型的列表以外的任何内容:

_T = TypeVar('_T')

# ...snip...

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

    # ...snip...

    def __add__(self, x: List[_T]) -> List[_T]: ...

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

我们有可能以某种方式扩展这种类型签名(例如,重载它以接受

List[_S]
并在这种情况下返回
List[Union[_T, _S]]
),但我认为没有人愿意研究这种方法的可行性:这种事情在实践中并不太有用,对于那些想要严格同类列表或想要子类化它们的人来说,生活变得更加困难,并且可能会破坏依赖于当前类型签名的“很多”现有代码。 这种类型签名也可能反映了 PEP 484 初始设计期间做出的更广泛的选择,即假设列表始终是同质的——始终包含相同类型的值。

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

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


基本上,只有当

lst1.__add__(lst2)

保证不会改变任一操作数时,您想要的行为才是安全的。但实际上并没有办法保证这一点——如果

lst1

 是一些奇怪的列表子类,它将项目从 
lst2
 复制到自身呢?然后暂时将 
lst1
 的类型从 
SomeListSubtype[int]
 放宽到 
SomeListSubtype[object]
 是不安全的:在从 
lst1
 添加/注入字符串后,
lst2
 将不再仅包含整数。
当然,实际上编写这样的子类也是不好的做法,但是类型检查器无法假设用户在不强制执行的情况下会遵循最佳实践:类型检查器、编译器和类似的工具从根本上来说都是保守的野兽。 

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


修改列表的类型签名(并确保它不会破坏任何现有代码)

    引入某种方式来声明方法是纯粹的——没有变异。基本上,将
  1. PEP 591
  2. 背后的想法概括为也适用于函数。 (但这需要编写 PEP、修改 typeshed 以使用新的类型构造、做大量仔细的设计和实现工作......)
  3. 当我们确定这两个变量不是列表的子类时,也许这种交互是特殊情况。 (但实际上,我们确定知道这一点的次数非常有限。)
  4. ...等等。
但是所有这些事情都需要时间和精力来完成:这是一个优先顺序问题。 PyCharm(和 mypy 等)的问题跟踪器相当长,并且不乏其他错误/功能请求需要解决。

其他评论中明确解释了原因,因此我只想为那些无法跳过代码中的串联步骤并且不想看到这个烦人的警告的人强调潜在的解决方法。

3
投票
在上述情况下,没有产生警告的操作:

list1 += list2

有时我不得不这样做:

[*list1, *list2]


就像 Pycharm 所说,这只是一个警告,您可以连接不同的对象或列表,但这被认为是一种不好的做法。

0
投票

© www.soinside.com 2019 - 2024. All rights reserved.