Typehint Union Dictionary branching error

Question:

I want to implement a function like the one below, but it throws a type hint warning.

def test(flag: bool)->Dict[str, int]| Dict[str, str]:
    a: Dict[str, str]| Dict[str, int] = {}
    if flag:
        a['a'] = 1
    else:
        a['a'] = 'hello'
    return post_process(a)

enter image description here

With the following warning by Pylance:

Argument of type "Literal[1]" cannot be assigned to parameter "__value" of type "str" in function "__setitem__"
  "Literal[1]" is incompatible with "str"PylancereportGeneralTypeIssues

I know the following is a solution, but it is not very semantically pleasing.
Since semantically, a and b are the same, but it appears to be different.

def test()->Dict[str,int]|Dict[str, str]:
    flag = True
    if flag:
        a: Dict[str, int] = {'a': 1}
        return a
    else:
        b: Dict[str, str] = {'a': 'hello'}
        return b
    if flag:
        return post_process(a)
    else:
        return post_process(b)

Is there a solution that looks like the code below, that does not throw type hint warnings?:

def test(flag: bool)->Dict[str, int]| Dict[str, str]:
    a: Dict[str, str]| Dict[str, int] = {}
    if flag:
        a: Dict[str, str]
        a['a'] = 1
    else:
        a: Dict[str, int]
        a['a'] = 'hello'
    return post_process(a)

Note that if a weren’t a dictionary and a literal type it could work:

def test(flag: bool)-> int|str:
    a: int| str
    if flag:
        a = 1
    else:
        a = 'hello'
    return post_process(a)

I want an implementation that do not contradict with the type hint system, without sacrificing any of the readability of the actual code. Thank you.

I know type hint warnings are not a part of the python protocol, but I am using Pylance linting system.

Answers:

Edit:
Since your comment outlined that you truly want the return value to be only ever a dict of strings or a dict of ints, and never a mix of both, you can use the @overload decorator to determine return types based on the boolean flag:

from typing import Dict, overload, Literal


@overload
def test(flag: Literal[True]) -> Dict[str, int]:
    ...


@overload
def test(flag: Literal[False]) -> Dict[str, str]:
    ...


def test(flag):
    a = {}
    if flag:
        a["a"] = 1
    else:
        a["a"] = "hello"
    return a


b = test(True)  # Intellisensed as Dict[str, int]

c = test(False)  # Intellisensed as Dict[str, str]

Original Post:

I was able to recreate your error and resolve it with Dict[str, str | int] like so:

def test(flag: bool)->Dict[str, int | str]:
    a: Dict[str, int | str] = {}
    if flag:
        a['a'] = 1
    else:
        a['a'] = 'hello'
    return a

Notice that if you swap the order of the int and str in a: Dict[str, int | str] = {}, then either assignment of a['a'] will fail. This helps indicate that your type hint suggests that the return value is either ONLY a dict of strings, or ONLY a dict of ints, but when intellisense reads your code, it sees that there’s a possibility of a variable dict type, that of strings AND integers. If you wanted to type the return to be either a dict ONLY containing strings based on a flag, or ONLY containing integers based on the same flag, then check out the @overload decorator from the typing library.

Answered By: Jamie.Sgro
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.