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

mypy "Optional[Dict[Any, Any]]" is not indexable inside standard filter, map

给定以下代码:

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'], [])

mypy 0.770 失败,在 bar 的最后一行出现以下错误:Value of type "Optional[Dict[Any, Any]]" is not indexablemap 也是如此。从 pydash 更改行以使用列表理解或 filter_map_ 可解决错误。

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

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

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

    if not d:
        return
    d_exists = d

    filter(lambda x: d_exists['k'], [])

d 未绑定到内部范围中较窄类型的原因可能是因为无法保证 d 不会在外部范围,例如:

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, [])

而如果您绑定一个新变量,则无法进行赋值:

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, [])

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

mypy 不模拟代码:它不知道 d 不是 None 如果实际上达到了对 filter 的调用。它只知道有人试图索引一些被静态标记为可能具有None作为值的东西。 (换句话说,d 的静态类型不会改变,除非你实际分配一个具有不同静态类型的值。)

您可以使用 cast 功能帮助 mypy

from typing import Optional, Dict<b>, cast</b>

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


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

    if not d:
        return

    <b>d: dict = cast(dict, d)  # "Trust me, mypy: d is a dict"</b>

    filter(lambda x: d['k'], [])