Skip to content

Unable to remove Optional wrapper when using Mapping[str, str] instead of dict #11618

Closed
@ajhynes7

Description

@ajhynes7

Hello, I'm not sure if this is a bug, but perhaps it'll help to clarify the difference between using dict and Mapping[str, str] as annotations.

This is a common pattern in Python to avoid mutable default arguments:

def foo(x: Optional[dict] = None):
    if x is None:
        x = {}

So I tried writing a function to capture this pattern.

from typing import Optional, TypeVar

T = TypeVar("T")


def assign_if_none(obj: Optional[T], assignment: T) -> T:

    return assignment if obj is None else obj

Now you can use it like this:

def foo(x: Optional[dict] = None):
    x = assign_to_none(x, {})

But mypy raises errors when I use a type annotation with Mapping or Dict instead of dict.

Full example:

from typing import Mapping, Optional, TypeVar

T = TypeVar("T")


def assign_if_none(obj: Optional[T], assignment: T) -> T:

    return assignment if obj is None else obj


def func_1(mapping: Optional[dict] = None) -> str:

    mapping = assign_if_none(mapping, {})

    if "key" in mapping:
        return "Included"

    return "Excluded"


def func_2(mapping: Optional[Mapping[str, str]] = None) -> str:

    mapping = assign_if_none(mapping, {})

    if "key" in mapping:
        return "Included"

    return "Excluded"

func_1 has no errors, but func_2 does.

demo.py:23: error: Incompatible types in assignment (expression has type "object", variable has type "Optional[Mapping[str, str]]")
demo.py:25: error: Unsupported right operand type for in ("Optional[Mapping[str, str]]")

I tried putting reveal_type(mapping) before and after the call to assign_if_none in both functions. Here are the results:

func_1:

demo.py:13: note: Revealed type is "Union[builtins.dict[Any, Any], None]"
demo.py:15: note: Revealed type is "builtins.dict[Any, Any]"

func_2:

demo.py:25: note: Revealed type is "Union[typing.Mapping[builtins.str, builtins.str], None]"
demo.py:27: note: Revealed type is "Union[typing.Mapping[builtins.str, builtins.str], None]"

So in func_1, mypy correctly infers that the call to assign_if_none removes the Optional wrapper. But this doesn't happen in func_2.

If this isn't a bug with mypy, is there a better way to annotate the function assign_if_none so it works in func_2?

Environment

  • Mypy version used: 0.910
  • Python version used: 3.8.12

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrongtopic-join-v-unionUsing join vs. using unions

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions