mypy "Optional[Dict[Any, Any]]" 在标准过滤器、地图内不可索引

Yul*_*ian 5 python type-hinting python-3.x mypy

给出以下代码:

from typing import Optional, Dict

def foo(b: bool) -> Optional[Dict]:
    return {} if b else None


def bar() -> None:
    d = foo(False)

    if not d:
        return

    filter(lambda x: d['k'], [])
Run Code Online (Sandbox Code Playgroud)

mypy 0.770 失败,最后一行出现以下错误barValue of type "Optional[Dict[Any, Any]]" is not indexable。同样适用于map. 将行更改为使用列表理解或pydash 中的filter_map_可以解决该错误。

filter即使有类型保护,为什么 mypy 在使用标准时也会抛出错误?

che*_*ner 7

mypy不模拟代码:它不知道是否d实际到达了None对的调用filter。它只知道尝试对静态标记为可能具有None值的内容进行索引。(换句话说,d除非您实际分配具有不同静态类型的值,否则 的静态类型不会更改。)

您可以mypy通过使用该cast功能来提供帮助。

from typing import Optional, Dict, cast

def foo(b: bool) -> Optional[Dict]:
    return {} if b else None


def bar() -> None:
    d = foo(False)

    if not d:
        return

    d: dict = cast(dict, d)  # "Trust me, mypy: d is a dict"

    filter(lambda x: d['k'], [])
Run Code Online (Sandbox Code Playgroud)

  • 我可能仍然会误解一些东西,但我不认为这解释了为什么使用列表理解或“filter_”可以在不给 mypy 额外提示的情况下工作。 (2认同)

Sam*_*ord 3

if在or之后发生的类型缩小assert不会传播到您绑定该变量的内部作用域。简单的解决方法是定义一个与较窄类型绑定的新变量,例如:

def bar() -> None:
    d = foo(False)

    if not d:
        return
    d_exists = d

    filter(lambda x: d_exists['k'], [])
Run Code Online (Sandbox Code Playgroud)

未绑定到内部作用域中较窄类型的原因d可能是因为无法保证不会在外部作用域中d更改回较窄类型,例如:None

def bar() -> None:
    d = foo(False)

    if not d:
        return

    def f(x: str) -> str:
        assert d is not None  # this is totally safe, right?
        return d['k']         # mypy passes because of the assert

    d = None  # oh no!
    filter(f, [])
Run Code Online (Sandbox Code Playgroud)

而如果您绑定一个新变量,则无法进行该分配:

def bar() -> None:
    d = foo(False)

    if not d:
        return
    d_exists = d

    def f(x: str) -> str:
        # no assert needed because d_exists is not Optional
        return d_exists['k']

    d_exists = None  # error: Incompatible types in assignment
    filter(f, [])
Run Code Online (Sandbox Code Playgroud)

在您的特定示例中,不存在运行时危险,因为lambda会立即进行评估,同时filter您没有机会进行更改d,但 mypy 不一定有一种简单的方法来确定您调用的函数不会挂起该 lambda 并稍后对其进行评估。