You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[email protected] regression: Argument 1 to "get" of "Mapping" has incompatible type #6597
wemake_python_styleguide/logic/arguments/function_args.py:126: error: Argument 1 to "get" of "Mapping" has incompatible type "Optional[Any]"; expected "str"
Simplier repro:
fromtypingimportMapping, Any, Optionalx: Optional[Any]
m: Mapping[str, int]
m.get(x)
# error: Argument 1 to "get" of "Mapping" has incompatible type "Optional[Any]"; expected "str"
So, what do you think: is this a valid error? Because it will work at runtime with no problem. And since it has Any part in it, sometimes it can even be str. So, in my app it was working as expected in all cases: if x is str and exists in m - then fine. If not - then just return None.
I am openning it here, because it looks like a typeshed issue, rather than a mypy issue.
We should probably change _KT to _KT | None in both overloads. Technically the first argument can be anything that overlaps with _KT, but in practice, allowing None is probably good enough.
The stricter typing for get() will catch potential type problems. While your example will work at runtime, it indicates a likely bug as None is not a valid key. I like the new behavior, although I could be convinced otherwise. Maybe we could also just special case None.
I hit this while working on my bidict library, which implements bidirectional mapping data structures which wrap two (regular one-directional) mappings, and which is type hinted and checked with mypy.
it indicates a likely bug
I disagree that this indicates a "likely" bug. It could be a bug. But it's just as likely that there is no bug. And the current type annotations are flagging perfectly correct and idiomatic code:
Sometimes you have no idea what type x is. One of the main reasons that d.get(x[, default]) and d.pop(x, default) exist -- with the optional default argument -- is to support coding in a "check the result after calling" style, rather than the "look before you leap" style, which is often less Pythonic.
Yet these APIs' type hints are treating these APIs the same as Mapping.__getitem__ and MutableMapping.__delitem__ respectively, even though they are not meant to be used the same way.
Would you accept a PR that changes the Mapping.get(x[, default]) and MutableMapping.pop(x, default) type hints to support the "check result after calling" style they're intended to support? Note this is more than just special-casing None.
I don't think that this is the way forward. Type checkers are supposed to check that the types are correct after all. If you don't know types or are not interested in the extra type checking the annotations provide, you can always use the Any escape hatch.
Type checkers are supposed to check that the types are correct after all.
It is not a type error to call dict.get() with a key of type that is not assignable to the key type of the dictionary. It is perfectly type-safe, may even return a value (consider that the current typeshed annotations prevent calling .get() on a dict[str, str] with a key of type object -- or on a dict[Literal["foo", "bar"], int) with a key of type str), and can be useful in correct code.
The current annotations in typeshed are overly restrictive and attempt to enforce a highly opinionated lint rule, not a type error. Type checkers and linters can easily implement such a lint rule as a special case, if their users want it, but the job of typeshed is to accurately annotate what APIs will accept without erroring, and dict.get() accepts any Python object.
Reacted by Joshua Bronson, Randolf Scholz and Michael H
(IMO the better version of the lint rule would be to only error if the provided key type is disjoint from the dictionary's key type -- that is, no value from the dictionary could ever be returned from the call -- rather than requiring assignability. But this is a rule that must be implemented as a special case, it can't be expressed in typeshed without negation types.)
(IMO the better version of the lint rule would be to only error if the provided key type is disjoint from the dictionary's key type -- that is, no value from the dictionary could ever be returned from the call -- rather than requiring assignability. But this is a rule that must be implemented as a special case, it can't be expressed in typeshed without negation types.)
See python/typing#2154 for a potential solution to this. I disagree that this is a "lint rule", though. This falls squarely in what type checkers are supposed to check.
Activity
Akuli commentedon Dec 16, 2021
typeshed/stdlib/typing.pyi
Lines 459 to 462 in 9aa66f0
We should probably change
_KTto_KT | Nonein both overloads. Technically the first argument can be anything that overlaps with_KT, but in practice, allowingNoneis probably good enough.srittau commentedon Dec 16, 2021
The stricter typing for
get()will catch potential type problems. While your example will work at runtime, it indicates a likely bug asNoneis not a valid key. I like the new behavior, although I could be convinced otherwise. Maybe we could also just special caseNone.jab commentedon Jan 31, 2022
I hit this while working on my bidict library, which implements bidirectional mapping data structures which wrap two (regular one-directional) mappings, and which is type hinted and checked with mypy.
I disagree that this indicates a "likely" bug. It could be a bug. But it's just as likely that there is no bug. And the current type annotations are flagging perfectly correct and idiomatic code:
Sometimes you have no idea what type
xis. One of the main reasons thatd.get(x[, default])andd.pop(x, default)exist -- with the optionaldefaultargument -- is to support coding in a "check the result after calling" style, rather than the "look before you leap" style, which is often less Pythonic.Yet these APIs' type hints are treating these APIs the same as
Mapping.__getitem__andMutableMapping.__delitem__respectively, even though they are not meant to be used the same way.Would you accept a PR that changes the
Mapping.get(x[, default])andMutableMapping.pop(x, default)type hints to support the "check result after calling" style they're intended to support? Note this is more than just special-casingNone.Thank you for your consideration.
srittau commentedon Jan 31, 2022
I don't think that this is the way forward. Type checkers are supposed to check that the types are correct after all. If you don't know types or are not interested in the extra type checking the annotations provide, you can always use the
Anyescape hatch.Akuli commentedon Jan 31, 2022
We could probably make it work well enough in practice by special-casing
Nones.carljm commentedon Jan 16, 2026
It is not a type error to call
dict.get()with a key of type that is not assignable to the key type of the dictionary. It is perfectly type-safe, may even return a value (consider that the current typeshed annotations prevent calling.get()on adict[str, str]with a key of typeobject-- or on adict[Literal["foo", "bar"], int)with a key of typestr), and can be useful in correct code.The current annotations in typeshed are overly restrictive and attempt to enforce a highly opinionated lint rule, not a type error. Type checkers and linters can easily implement such a lint rule as a special case, if their users want it, but the job of typeshed is to accurately annotate what APIs will accept without erroring, and
dict.get()accepts any Python object.dict.getoverload with default value not be generic? #9155 as a duplicate of this issuecarljm commentedon Jan 16, 2026
(IMO the better version of the lint rule would be to only error if the provided key type is disjoint from the dictionary's key type -- that is, no value from the dictionary could ever be returned from the call -- rather than requiring assignability. But this is a rule that must be implemented as a special case, it can't be expressed in typeshed without negation types.)
srittau commentedon Jan 19, 2026
See python/typing#2154 for a potential solution to this. I disagree that this is a "lint rule", though. This falls squarely in what type checkers are supposed to check.