简体   繁体   中英

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

From reading through mypy issues, it seems as if calling dict.update() , and supplying a TypedDict is not type safe. This does not make sense to me.

My question is (particularly from the 2nd issue, linked below):

  • Since a TypedDict is a dict at runtime, why does mypy complain about not being able to pass a TypedDict into dict.update , saying it expects a Mapping ?
    • In my mind, dict is like saying Dict[Any, Any] , so why not add a TypedDict as a dict value?
    • In your answer,can you please provide a concrete example?
  • In general, why is calling dict.update(SomeTypedDict) not type safe?

There are two examples of this, found in mypy issues:

  1. python/mypy #6462 : TypedDict update() does not accept TypedDict with compatible subset keys

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

  1. python/mypy #9086 : False positive [arg-type] error when updating a dict with a TypedDict

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).


Sample Code from 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 says that:

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.

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)

Update: Let us consider your sample

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]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM