Skip to content

Mapping with Union for keys #8477

Closed
Closed

Description

@RobertoPrevato

Hi,
Considering the following example:

from typing import *

def example(value: Mapping[Union[str, int], Union[str, int]]):
    ...

x: Mapping[str, int] = {
    "a": 1,
    "b": 2,
    "c": 3
}

example(x) # <-- MyPy doesn't like this: Argument 1 to "example" has incompatible type "Mapping[str, int]"; expected "Mapping[Union[str, int], Union[str, int]]"mypy(error)

Results in error: Argument 1 to "example" has incompatible type "Mapping[str, int]"; expected "Mapping[Union[str, int], Union[str, int]]"mypy(error).

Shouldn't MyPy accept the Mapping[str, int] to be a valid instance of Mapping[Union[str, int], Union[str, int]]?

This behavior looks surprising, because Union is supported for values, but not for keys. If the function annotation is modified this way:

from typing import *


def example(value: Union[Mapping[str, Union[str, int]],
                         Mapping[int, Union[str, int]]]):
    ...


x: Mapping[str, int] = {
    "a": 1,
    "b": 2,
    "c": 3
}

example(x)

Then mypy is happy. Version: mypy==0.761

Activity

  1. RobertoPrevato commented on Mar 2, 2020

    @RobertoPrevato
    Author

    Additionally:

    a: Union[Mapping[str, Union[str, int]],
             Mapping[int, Union[str, int]]]
    
    b: Union[str, int] = 20
    
    c = a[b]  < mypy reports two errors, written below
    Invalid index type "Union[str, int]" for "Union[Mapping[str, Union[str, int]], 
    Mapping[int, Union[str, int]]]"; expected type "str" mypy(error)
    
    Invalid index type "Union[str, int]" for "Union[Mapping[str, Union[str, int]], 
    Mapping[int, Union[str, int]]]"; expected type "int" mypy(error)
    
  2. JelleZijlstra commented on Mar 3, 2020

    @JelleZijlstra
    Member

    This is generally because of https://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance. The key argument to Mapping is invariant. I don't remember exactly why this is, but I think we tried making it covariant at some point and it caused problems, so we had to revert.

  3. hauntsaninja commented on Mar 3, 2020

    @hauntsaninja
    Collaborator

    It's because __getitem__ and get could do unsound things.
    python/typing#445
    #1114

  4. RobertoPrevato commented on Mar 3, 2020

    @RobertoPrevato
    Author

    @JelleZijlstra, @hauntsaninja thank you for your replies! 🌞 I thought there was a good reason for this behavior.
    I am closing this issue, since I see it was already discussed.

    FWIW, I think I understood that Union is not the right tool to do the think I had in mind, described in my example above. The right tool is to define generic types for keys and values!

    from typing import *
    
    
    A = TypeVar("A", str, int)
    B = TypeVar("B", str, int)
    
    
    def example2(value: Mapping[A, B]):
        ...
    
    
    x: Mapping[str, int] = {
        "a": 1,
        "b": 2,
        "c": 3
    }
    
    example2(x)
    
    y: Mapping[str, str] = {
        "a": "1",
        "b": "2",
        "c": "3"
    }
    
    example2(y)

    And I understood this, because in C# it would be the same (Union doesn't exist in C#, you have to use interfaces to describe something like union). I often take C# as example because I know it, it's static, and clearly it is mature about generics.

    A basic example would be:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace netconsole
    {
        class Program
        {
            // a function that accepts any kind of dictionary as input and returns 
            // a new dictionary of only strings
            static Dictionary<string, string> Example<A, B>(Dictionary<A, B> dict)
                => dict.ToDictionary(key => key.ToString(), value => value.ToString());
    
            static void Main(string[] args)
            {
                var x = new Dictionary<string, int>() {
                    {"a", 1},
                    {"b", 2},
                    {"c", 3}
                };
    
                var a = Example(x);
            }
        }
    }
  5. RobertoPrevato commented on Mar 3, 2020

    @RobertoPrevato
    Author

    PS. I see the same was discussed here #6001

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions