Mypy error "Need more than 2 values to unpack (3 expected)" although union of different tuples is used

Viewed 116

When I run mypy on the following code I get

Need more than 2 values to unpack (3 expected)

How do I fix this?

def func(third: bool = False) -> tuple[int, int] | tuple[int, int, int]:
    if third:
        return 1, 2, 3
    else:
        return 1, 2
        
a, b, c = func(True) # mypy error line

func is in fact returning three values in this case. I'm using Python 3.10.4.

3 Answers

You could use overload to declare that func has a different return type depending on the argument.

from typing import Literal, overload

@overload
def func(third: Literal[True]) -> tuple[int, int, int]:
    ...
@overload
def func(third: Literal[False]) -> tuple[int, int]:
    ...
def func(third: bool = False):
    if third:
        return 1, 2, 3
    else:
        return 1, 2

Described situation is a typical use case for overloading.

You can annotate the function such that for True argument it returns 3-tuple and for False - 2-tuple:

from typing import overload, Literal

@overload
def func(third: Literal[True]) -> tuple[int, int, int]: 
    ...
@overload
def func(third: Literal[False] = ...) -> tuple[int, int]:
    ...
    
def func(third: bool = False) -> tuple[int, int] | tuple[int, int, int]:
    if third:
        return 1, 2, 3
    else:
        return 1, 2
        
a, b, c = func(True)
a, b = func(False)
a, b = func()

Playground

The problem in your current code is that mypy doesn't know whether return type will be 2-tuple or 3-tuple. It does not make additional assumptions looking at implementation, type hints are strict. Your code could look like this (interpreted by mypy exactly the same way):

import random
def func(three: bool = False) -> tuple[int, int] | tuple[int, int, int]:
    if random.random() < 0.5:
        return 1, 1
    else:
        return 1, 1, 1

a, b, c = func()

... And now your code fails in (approx) 50% cases and mypy points to that error.

It is a known issue with Mypy: Cannot unpack Union of Tuples of different length.

Here is my proposal how to fix the given example, so it works and Mypy shows no errors:

from typing import Optional, Tuple


def func(third: bool = False) -> Tuple[int, int, Optional[int]]:
    if third:
        return 1, 2, 3
    else:
        return 1, 2, None


a, b, c = func(True)

print(a, b, c)

Output:

1 2 3

Mypy run:

Success: no issues found in 1 source file
Related