如果传入 TypedDict,Python 使用 dict.update 类型不安全?
Python is using dict.update not type safe if passing in a TypedDict?
通读 mypy
问题,似乎调用 dict.update()
并提供 TypedDict
类型不安全。这对我来说没有意义。
我的问题是(特别是第 2 期,链接如下):
- 因为
TypedDict
在运行时是 dict
,为什么 mypy
抱怨无法将 TypedDict
传递给 dict.update
,说它需要 Mapping
?
- 在我看来,
dict
就像说 Dict[Any, Any]
,那么为什么不添加一个 TypedDict
作为 dict 值呢?
- 在你的回答中,能否提供一个具体的例子?
- 一般来说,为什么调用
dict.update(SomeTypedDict)
类型不安全?
在 mypy
个问题中找到了两个这样的例子:
- python/mypy #6462: TypedDict update() 不接受具有兼容子集键的 TypedDict
This is a pretty subtle issue. The update call is arguably not type safe.
- python/mypy #9086:使用 TypedDict
更新字典时出现误报 [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]
通读 mypy
问题,似乎调用 dict.update()
并提供 TypedDict
类型不安全。这对我来说没有意义。
我的问题是(特别是第 2 期,链接如下):
- 因为
TypedDict
在运行时是dict
,为什么mypy
抱怨无法将TypedDict
传递给dict.update
,说它需要Mapping
?- 在我看来,
dict
就像说Dict[Any, Any]
,那么为什么不添加一个TypedDict
作为 dict 值呢? - 在你的回答中,能否提供一个具体的例子?
- 在我看来,
- 一般来说,为什么调用
dict.update(SomeTypedDict)
类型不安全?
在 mypy
个问题中找到了两个这样的例子:
- python/mypy #6462: TypedDict update() 不接受具有兼容子集键的 TypedDict
This is a pretty subtle issue. The update call is arguably not type safe.
- python/mypy #9086:使用 TypedDict 更新字典时出现误报 [arg-type] 错误
Since TypedDict objects use structural subtyping, there could be additional items not visible through the type
Foo
, with arbitrary value types. ThusMapping[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 withMapping[str, object]
. Second, aTypedDict
type A is consistent withTypedDict
B if A is structurally compatible with B.
A
TypedDict
with all int values is not consistent withMapping[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]