Reputation: 61
Is it possible with Python type hints to specify the types of a dictionary's keys and values as pairs ?
For instance :
int
, value should be a str
str
, value should be an int
If I write :
Dict[Union[int, str], Union[int, str]]
it allows str -> str
and int -> int
, which are not allowed.
And with :
Union[Dict[int, str], Dict[str, int]]
the dictionary can be either a Dict[int, str]
or Dict[str, int]
, but not both at the same time ...
I also looked into TypedDict
, but it requires to give explicitly all the keys.
Upvotes: 6
Views: 7115
Reputation: 31
If using typing.cast
is acceptable for your application, then this can be done by making a class that subclasses Dict
that has overrides for __setitem__
and __getitem__
, casting your dict to that type. From then on, type checkers will infer correct KeyType: ValueType
pairs.
The caveats of this approach are that you can't use it to type check proper dict construction, as that happens before the cast. Additionally, you would need to add more overrides for things like update
, __iter__
and possibly other dict methods to get type checking for things beyond __setitem__
/__getitem__
dict access.
Example:
from typing import Dict, overload, cast
class KVTypePairedDict(Dict):
@overload
def __getitem__(self, key: str) -> int: ...
@overload
def __getitem__(self, key: int) -> str: ...
def __getitem__(self, key): ...
@overload
def __setitem__(self, key: str, value: int) -> None: ...
@overload
def __setitem__(self, key: int, value: str) -> None: ...
def __setitem__(self, key, value): ...
test: KVTypePairedDict = cast(KVTypePairedDict, {"foo": 0, 1: "bar"})
# str keys
a: int = test["foo"]
test["foo"] = 0
c: str = test["foo"] # <-- mypy and PyCharm flag this
test["foo"] = "bar" # <-- mypy and PyCharm flag this
# int keys
d: str = test[1]
test[1] = "bar"
b: int = test[0] # <-- mypy and PyCharm flag this
test[1] = 0 # <-- mypy and PyCharm flag this
Upvotes: 3