如果传入 TypedDict,Python 使用 dict.update 类型不安全?

Python is using dict.update not type safe if passing in a TypedDict?

通读 mypy 问题,似乎调用 dict.update() 并提供 TypedDict 类型不安全。这对我来说没有意义。

我的问题是(特别是第 2 期,链接如下):


mypy 个问题中找到了两个这样的例子:

  1. python/mypy #6462: TypedDict update() 不接受具有兼容子集键的 TypedDict

This is a pretty subtle issue. The update call is arguably not type safe.

  1. python/mypy #9086:使用 TypedDict
  2. 更新字典时出现误报 [arg-type] 错误

Since TypedDict objects use structural subtyping, there could be additional items not visible through the type Foo, with arbitrary value types. Thus Mapping[str, object] is correct (though it is unintuitive).


示例代码 来自 python/mypy #9086

from typing import TypedDict

class Foo(TypedDict):
    baz: int

foo: Foo = {"baz": 9000}

# spam is a regular dict, yet mypy errors out when trying to add a TypedDict
# to it.  This doesn't make sense to me, when a regular dict should be like
# saying equal Dict[Any, Any]
spam = {"ham": {"eggs": 5}}
spam["ham"].update(foo)  # error: Argument 1 to "update" of "dict" has
# incompatible type "Foo"; expected "Mapping[str, int]"  [arg-type]

PEP 589 表示:

First, any TypedDict type is consistent with Mapping[str, object]. Second, a TypedDict type A is consistent with TypedDict B if A is structurally compatible with B.

A TypedDict with all int values is not consistent with Mapping[str, int], since there may be additional non-int values not visible through the type, due to structural subtyping. These can be accessed using the values() and items() methods in Mapping, for example.

示例:

class A(TypedDict):
    x: int

class B(TypedDict):
    x: int
    y: str

def sum_values(m: Mapping[str, int]) -> int:
    n = 0
    for v in m.values():
        n += v  # Runtime error
    return n

def f(a: A) -> None:
    sum_values(a)  # Error: 'A' incompatible with Mapping[str, int]

b: B = {'x': 0, 'y': 'foo'}
f(b)

更新:让我们考虑一下您的示例

from typing import TypedDict

class Foo(TypedDict):
    baz: int

foo: Foo = {"baz": 9000}

# spam here is not equal Dict[Any, Any]. mypy will infer type for it 
# as Dict[str, Dict[str, int]]. Consequently, update() method for its 
# item below will expect Mapping[str, int]  
spam = {"ham": {"eggs": 5}}
# If you try to do empty dict as below which indeed has type Dict[Any, Any]
# spam = {}  # mypy will complain on it "Need type annotation for 'spam'"
spam["ham"].update(foo)  # error: Argument 1 to "update" of "dict" has 
# incompatible type "Foo"; expected "Mapping[str, int]"  [arg-type]