Pylance要求在符合列表变量上显式指定类型

3

我将一个类型定义为由Literal字符串组成的Union

Color = Literal[
    "red",
    "green",
    "blue",
    "yellow",
    "orange",
    "purple"
]

我有一个函数,它期望一个符合类型的字符串列表。
def f(colors: List[Color]):
    ...

我实例化了一个符合要求的字符串列表,并将其传递给函数

colors = ['blue', 'green']

f(colors)

Pylance 用警告标记了函数调用

Argument of type "list[str]" cannot be assigned to parameter "colors" of type "List[Color]" in function "f"
  "list[str]" is incompatible with "List[Color]"
    TypeVar "_T@list" is invariant
      Type "str" cannot be assigned to type "Color"
        "str" cannot be assigned to type "Literal['red']" Pylance(reportGeneralTypeIssues)

如果我明确地注释实例化的列表,警报就会消失

colors: List[Color] = ['blue', 'green']

这似乎是多余的。无论我是否注释,列表都与预期类型匹配。难道类型系统不应该认识到这一点吗?

事实上,我可以直接传递列表而不会有任何警告。

f(['blue', 'green']) # Pylance allows

Pylance也可以胜任这个任务

def f(seven: Literal[7]):
    ...
    
x = 7
f(x) # Pylance allows, doesn't require doing x: int = 7

所以它似乎只抱怨列表变量,而不是一般的隐式类型变量。

为什么我必须明确注释那些其值明确符合预期类型的列表变量呢?


1
这是因为你的列表是一个字符串列表,但是你的类型注释告诉Pylance该列表是颜色对象列表。 - Hyalunar
@Hyalunar 为什么我执行 def f(seven: Literal[7]); x = 7; f(x) 时不会出现相同的错误?x 是一个 int,恰好是 7,而 f 需要的是 Literal[7]。难道 x = 7 实际上被定义为 Literal[7],这恰好是 int 的子集,而类型系统无法轻松地知道 ['blue','green'] 是否是 Literal['red','green','blue', ...] 的子集吗? - Michael Moreno
1个回答

3
Python中的文本字面值(如'blue'7)具有自然类型。根据它们的运行时类型,'blue'str,而7int。类型字面值可能会对此造成一些困惑,因为虽然它们采用特定对象并赋予它们附加的语义意义(例如'blue'Color7f()的参数),但它们不会覆盖默认的假定类型,并且只有在必要时才作为变量的真实类型锁定。
那么这会导致你遇到的行为是什么呢?让我们先处理7。在执行x = 7后,x的隐式自然类型不是“与f()兼容的类型”,而是int。在f(x)期间成功缩小了隐式类型,但仅在该调用的上下文中,并且仅当Pylance知道x可以合法地缩小为7时才能缩小。
同时,对于colors = ['blue', 'green'],Pylance只关心为colors提供正确的类型。基于文字,它首先看到它是一个列表,然后看到它是一个字符串列表。因此,推断出的类型是list[str]。现在,当我们到达f(colors)时,是否可以将其缩小为list[Color]?不行,因为列表是可变的。没有运行时保证列表不会以其他字符串结束,因此无法将列表中的字面值缩小为颜色。再次对比,7是一个不可变的int,因此我们知道不能在x中存储任何其他内容。那么为什么f(['green', 'blue'])有效呢?因为在这种情况下,列表的生命周期只有函数调用本身,因此存在隐式请求使列表与调用签名兼容(如果可能),而不必担心调用代码之后的操作(因为它根本不能做任何事情)。
那么如何绕过这个问题呢?第一种方法是你已经注意到的,显式限制列表为Colors。您还可以使用不可变类型(例如元组),因为Python知道,就像在函数调用中创建的列表一样,在运行时对象不会更改:
Color = Literal['red', 'blue']

def f(colors: Sequence[Color]): ...

L = ['red', 'blue']
f(L)  # fails!
T = ('red', 'blue')
f(T)  # succeeds!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接