Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mapping with Union for keys #8477

Closed
RobertoPrevato opened this issue Mar 2, 2020 · 5 comments
Closed

Mapping with Union for keys #8477

RobertoPrevato opened this issue Mar 2, 2020 · 5 comments

Comments

@RobertoPrevato
Copy link

RobertoPrevato commented Mar 2, 2020

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

@RobertoPrevato
Copy link
Author

RobertoPrevato commented Mar 2, 2020

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)

@JelleZijlstra
Copy link
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.

@hauntsaninja
Copy link
Collaborator

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

@RobertoPrevato
Copy link
Author

RobertoPrevato commented Mar 3, 2020

@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);
        }
    }
}

@RobertoPrevato
Copy link
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
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants