Description
🚀 Feature
I often have occasion to write code like:
from typing import Dict
a: Dict[str, str] = {"x": "y"}
b: Dict[str, str] = {"y": "z"}
def f(x: str) -> str:
f_a = a.get(x)
reveal_type(f_a) # Optional[str]
f_b = b.get(f_a, "default") # error: Argument 1 to "get" of "Mapping" has incompatible type "Optional[str]"; expected "str"
mypy rejects this code with the above-mentioned error. However, it seems natural to traverse the dictionaries in this way, providing a default value only at the end of the chain of lookups to preserve the contract of the function f
. My proposal is, for a dictionary with keys of type K
, to allow Optional[K]
as the first argument to get()
(and not strictly K
) as is the case now.
Python is dynamic enough that in principle the argument type of get
could be Any
-- it will never be a runtime error and the default will always be returned if the key is not found. It doesn't seem right to relax the argument type that far, though: it would make mypy miss obvious errors (like using an int
as a key passed to get
on a dict of str
s). But, because of the use case sketched above, None
feels different. As hinted by the name Optional[T]
(supplementing Union[T, NoneType]
), it often represents a missing value of type T, not its own independent type. That's why it feels correct, to me, to allow it as an argument to get
.
(This is, in a way, a similar pattern to what's enabled in JS by the .?
operator. That operator is a bit different since it deals with missing values not missing keys, but the logical operation feels somehow comparable: performing a nested lookup while safely accounting for possibly missing elements at any level.)