Skip to content

Allow passing None to Mapping.get #9430

Closed as not planned
Closed as not planned
@aecay

Description

@aecay

🚀 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 strs). 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.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions