Consider the following code:
def foo(a: dict[str | tuple[str, str], str]) -> None:
pass
def bar(b: dict[str, str]) -> None:
foo(b)
def baz(b: dict[tuple[str, str], str]) -> None:
foo(b)
foo({"foo": "bar"})
foo({("foo", "bar"): "bar"})
When checked with mypy in strict mode it produces the following errors:
file.py:6: error: Argument 1 to "foo" has incompatible type "Dict[str, str]"; expected "Dict[Union[str, Tuple[str, str]], str]"
file.py:9: error: Argument 1 to "foo" has incompatible type "Dict[Tuple[str, str], str]"; expected "Dict[Union[str, Tuple[str, str]], str]"
Which doesn't seem to make sense to me. The parameter is defined to accept a dict with either a string or a tuple as keys and strings as values. However, both variants are not accepted when explicitly annotated as such. They do however work when passing a dict like this directly to the function. It seems to me that mypy expects a dict that has to be able to have both options of the union as keys. I fail to understand why? If the constraints for the key are to be either a string or a tuple of to strings, passing either should be fine. Right? Am I missing something here?
CodePudding user response:
A dict[str | tuple[str, str], str] isn't just a dict with either str or tuple[str, str] keys. It's a dict you can add more str or tuple[str, str] keys to.
You can't add str keys to a dict[tuple[str, str], str], and you can't add tuple[str, str] keys to a dict[str, str], so those types aren't compatible.
If you pass a literal dict directly to foo (or to bar or baz), that literal has no static type. mypy infers a type for the dict based on the context. Many different types may be inferred for a literal based on its context. When you pass b to foo inside bar or baz, b already has a static type, and that type is incompatible with foo's signature.
CodePudding user response:
What would work here is
def foo(a: dict[str, str] | dict[tuple[str, str], str]) -> None:
pass
Or you would have to help mypy and explicitly type annotate the dicts you are passing like
mydict : dict[str | tuple[str, str], str] = {"a" : "b"}
foo(mydict) # your foo as typed in your example
