VSCode:使用非本地导致变量类型 "Never"

VSCode: Using nonlocal causes variable type "Never"

我发现 VSCode/Pylance 和一些 Python 代码有奇怪的行为。考虑以下最小示例:

#!/usr/bin/env python3

from typing import Optional

def main():
    x: Optional[list] = None
    def f():
        nonlocal x
        x = [10]
    f()
    assert isinstance(x, list)
    y: int = x[0] + 3
    assert isinstance(y, int)
    print(y)

main()

对 f() 的调用显然将外部 x 设置为 [10]。将鼠标悬停在第一个 assert 语句(在函数调用之后)的 x 上会显示 (variable) x: None,这是可以的。但是在 assert 语句之后,将鼠标悬停在 x 上会显示 (variable) x: Never。这会导致 x 在所有后续行中的语法突出显示和自动完成都失败,因为 VSCode/Pylance 显然看到了一个具有某种“无类型”的变量。输入 x. 甚至不会显示对象的内置成员,如 x.__class__x.__doc__。将 x 分配给另一个类型变量没有帮助。第一个断言导致 mypy 接受代码,而 PyLint 说 Value 'x' is unsubscriptable,因此其他工具之间似乎也存在一些分歧。这有点烦人,因为代码可以正常工作。

问题:这是 Pylance 中的错误吗?甚至预期的行为?有什么办法可以强制 Pylance 查看 x 的正确类型?

工具版本:

$ code --version
1.56.2
054a9295330880ed74ceaedda236253b4f39a335
x64
$ pylint --version
pylint 2.8.2
astroid 2.5.6
Python 3.8.5 (default, Jan 27 2021, 15:41:15) 
[GCC 9.3.0]
$ mypy --version
mypy 0.800

VSCode Python: v2021.5.842923320
VSCode 皮兰斯:v2021.5.3

谢谢你和最诚挚的问候,
菲利普

编辑:在 GitHub

上发布了问题

虽然将鼠标悬停在 x 上显示 (variable) x: Never,但在 Settings.json 中添加 "debug.inlineValues": true 然后 开始调试,可以看到x是一个特殊的变量,它的属性__class__确实是list:

当设置 Jedi 为语言服务器时,悬停在 x 上是 NoneTypelist:

您可以在 github/pylance-release 中寻求更多帮助。

根据GitHub上的讨论,我将回答如下问题:

  • 这是Pylance的bug吗?不是bug,但是Pyright(Pylance的类型检查器)中实现的类型推断没有考虑局部突变由外部执行流程引起(例如包含 nonlocal 的回调函数的 运行)。

  • 甚至是预期的行为?是的,至少部分是,考虑到以上几点。

  • 有没有办法强制Pylance看到x的正确类型?是的,在[=之后使用x = cast(list, x) 14=].

使用 cast,最小示例如下所示:

#!/usr/bin/env python3

from typing import Optional, cast

def main():
    x: Optional[list] = None
    def f():
        nonlocal x
        x = [10]
    f()
    assert isinstance(x, list)
    x = cast(list, x)
    y: int = x[0] + 3  # Now x is reported as '(variable) x: list'
    assert isinstance(y, int)
    print(y)

main()

进一步总结 GitHub 问题中的解释:行 x: Optional[list] = None 使 x 成为 None 类型,因为这就是它的实际情况,无论类型如何声明。现在,缺少对函数f内部x突变的分析,当到达assert isinstance(x, list)行时,x的类型仍然是None。由于类型 None 不是 list 的实例,对于类型检查器来说,这一行永远不会通过。为了表明这一点,x 的类型因此设置为 Never.