I'm trying to remove the Any
type hint from code similar to the following:
from typing import TypedDict, Any
class NestedDict(TypedDict):
foo: str
class EventDict(TypedDict):
nested: NestedDict
class BaseEventDict(TypedDict):
nested: Any # this should accept NestedDict but also other TypedDicts which may contain additional fields
test_dict: EventDict = {
"nested": {"foo": "abc"},
}
def print_dict(source_dict: BaseEventDict):
print(source_dict)
print_dict(test_dict)
Since the nested
field can contain either NestedDict
or other TypedDict
s with additional fields (for other EventDict
s), I've not been able to come up with a compatible TypedDict
(mypy
complains about extra keys). I thought Mapping[str, object]
might work in Any
's place, since [A]ny TypedDict type is consistent with Mapping[str, object]. However, mypy
complains with Argument 1 to "print_dict" has incompatible type "EventDict"; expected "BaseDict"
. Is there anything I can use instead of Any
, which essentially disables the check? Also, any insights into why Mapping[str, object]
is not a valid type here?
TypedDict
fields are invariant, because TypedDict
is a mutable structure. The reasoning behind that is explained in PEP589 in detail. So, to accept a TypedDict
with a field of type "some TypedDict
or anything compatible with it" you can use a generic solution:
from __future__ import annotations
from typing import TypedDict, Generic, TypeVar
class NestedDict(TypedDict):
foo: str
_T = TypeVar('_T', bound=NestedDict)
class BaseEventDict(Generic[_T], TypedDict):
nested: _T # this should accept NestedDict but also other TypedDicts which may contain additional fields
BaseEventDict
is parametrized with a type of its field, which is bound to NestedDict
- this way T
can be substituted only with something compatible with NestedDict
. Let's check:
class GoodNestedDict(TypedDict):
foo: str
bar: str
class BadNestedDict(TypedDict):
foo: int
class EventDict(TypedDict):
nested: NestedDict
class GoodEventDict(TypedDict):
nested: GoodNestedDict
class BadEventDict(TypedDict):
nested: BadNestedDict
# Funny case: lone TypeVar makes sense here
def print_dict(source_dict: BaseEventDict[_T]) -> None:
print(source_dict)
test_dict: EventDict = {
"nested": {"foo": "abc"},
}
good_test_dict: GoodEventDict = {
"nested": {"foo": "abc", "bar": "bar"},
}
bad_test_dict: BadEventDict = {
"nested": {"foo": 1},
}
print_dict(test_dict)
print_dict(good_test_dict)
print_dict(bad_test_dict) # E: Value of type variable "_T" of "print_dict" cannot be "BadNestedDict" [type-var]
In this setup print_dict
is also interesting: you cannot use an upper bound, because the field type is invariant, so a single TypeVar
with a bound (same as before) comes to rescue. Anything compatible with NestedDict
is accepted as _T
resolver, and everything incompatible is rejected.
Here's a playground with this implementation.