Why does mypy fail to detect that a raw tuple value is a NamedTuple?

352 Views Asked by At

I have this piece of code:

from typing import NamedTuple

class KeyTuple(NamedTuple):
    key: str

storage: dict[KeyTuple, int] = {}

storage.get(("kek",))

that raises an error:

test_mypy.py:8: error: No overload variant of "get" of "dict" matches argument type "Tuple[str]"  [call-overload]
test_mypy.py:8: note: Possible overload variants:
test_mypy.py:8: note:     def get(self, KeyTuple, /) -> Optional[int]
test_mypy.py:8: note:     def [_T] get(self, KeyTuple, Union[int, _T], /) -> Union[int, _T]

But if I try with:

storage: dict[tuple[str], int] = {}

storage.get(("kek",))

mypy doesn't raise any errors.

I expected no errors with the NamedTuple variant.

1

There are 1 best solutions below

0
user2357112 On BEST ANSWER

Unlike with, say, dict and typing.TypedDict, a regular tuple is never an instance of a namedtuple class. After all, a regular tuple's elements can't be accessed by name. A namedtuple class is a tuple subclass that adds additional functionality.

When you annotate your dict as dict[KeyTuple, int], you tell mypy that all keys will be of type KeyTuple. Unlike, say, a Java Map, this applies to retrieval operations, too, not just insertion. It is a static typing error to try to call dict.get with a key of the wrong type. Since a regular tuple is not an instance of KeyTuple, storage.get(("kek",)) is invalid.

Either call get with a KeyTuple:

storage.get(KeyTuple('kek'))

or annotate your dict as having regular tuple keys:

storage: dict[tuple[str], int] = {}