Open
Description
Bug Report
from typing import Optional, Dict
def test(arg: str) -> None:
...
def this_fails(arg: Dict[str, Optional[str]]) -> None:
if arg.get("key") is not None:
test(arg["key"]) # error
def this_fails_also(arg: Dict[str, Optional[str]]) -> None:
if arg.get("key") is not None:
test(arg.get("key")) # error
def but_this_pass(arg: Dict[str, Optional[str]]) -> None:
if arg.get("key") and arg["key"] is not None:
test(arg["key"])
def this_pass_also_but_is_unsafe(arg: Dict[str, Optional[str]]) -> None:
if arg["key"] is not None:
test(arg["key"])
First two methods fail due to Argument 1 to "test" has incompatible type "Optional[str]"; expected "str"
error.
Mypy seems to see no relation between arg["key"]
and arg.get("key")
and it should (IMHO).
I'm not sure if this is a bug and not a feature request, but arg.get("key") and arg["key"] is not None
looks redundant for me.
Expected Behavior
The possibility of arg["key"] is None
should be excluded by if arg.get("key") is not None
because the inner block is unreachable if "key in arg and arg["key"] is None
so the value is no longer Optional.
Environment
mypy 0.942
Python 3.10.0
Metadata
Metadata
Assignees
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
DevilXD commentedon Mar 26, 2022
There's no type-narrowing done via the
get
method, so this is more of a feature request rather than a bug. Callingget
will always returnOptional[...]
/... | None
and getting the value via key lookup always returns just the value type, no matter if it's actually in the dictionary or not. In my opinion, using theget
method to narrow the type here is completely unnecessary and leads to general code clutter. If you need to call sometest
function depending on some value existing in the dictionary (like in the examples you've provided), there's two approaches:Both are easy to read, perfectly valid code and type-check just fine.
spikethehobbitmage commentedon Jan 17, 2025
@DevilXD
Your first and third examples are more cluttery than OP's first example, and the second doesn't (and shouldn't) pass because arg["key"] can be None. It should read
if "key" in arg and arg["key"] is not None:
, which is also more cluttery than necessary.DevilXD commentedon Jan 17, 2025
I don't think so, as my code only does exactly one lookup in every case, only with an extra containment test in the 2nd example. Meanwhile, OP has double the lookups in each of their examples, and even triple in the 3rd case - that's way more than necessary. I'd say that's the real clutter right there.
That's correct. The main point of my examples was to show that the
get
method does no type-narrowing, and thus one should use either of those constructs (or something similar) to avoid the errors mentioned in this issue. I personally usually go with the 3rd option myself.spikethehobbitmage commentedon Jan 18, 2025
I find the OP's first example to be easier to read, and it doesn't clutter the namespace with a temporary variable. OP's third example was an example of an undesirable workaround.
That
get
doesn't do type narrowing isn't in question, but whether or not it should. At presentmypy
is flagging valid code as invalid, which seems wrong.Another example:
This could be accomplished with a nested comprehension, but that adds unnecessary complexity.