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

Unpacking tuples of variable length #1178

Closed
mihneagiurgea opened this issue Jan 28, 2016 · 23 comments · Fixed by #16237
Closed

Unpacking tuples of variable length #1178

mihneagiurgea opened this issue Jan 28, 2016 · 23 comments · Fixed by #16237
Labels
false-positive mypy gave an error on correct code feature priority-1-normal topic-type-narrowing Conditional type narrowing / binder topic-union-types

Comments

@mihneagiurgea
Copy link

Given the following code:

from typing import Any, Dict, Tuple, Union

VarTuple = Union[Tuple[int, int], Tuple[int, int, int]]

def make_tuple():
    # type: () -> VarTuple
    return None

x = make_tuple()
a = b = c = 0
if len(x) == 3:
    a, b, c = x
else:
    a, b = x
    c = 0

mypy complains about the following error:

sandbox.py:12: error: 'Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]' object is not iterable
sandbox.py:14: error: 'Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]' object is not iterable
@JukkaL JukkaL added the bug mypy got something wrong label Jan 28, 2016
@JukkaL
Copy link
Collaborator

JukkaL commented Jan 28, 2016

The error message is incorrect. However, mypy may not be able to type check this properly since it doesn't recognize the len(x) == 3 check. So there seem to be two related issues:

  • Union types aren't recognized as iterables when type checking assignment statements.
  • len(x) == n checks can't be used to narrow down tuple types or unions. These could work similarly to isinstance checks.

The recommended way to work around the issue is to use casts:

if len(x) == 3:
    a, b, c = cast(Tuple[int, int, int], x)
else:
    a, b = cast(Tuple[int, int], x)
    c = 0

@ddfisher ddfisher modified the milestones: Future, 0.4.0 Mar 1, 2016
@JukkaL
Copy link
Collaborator

JukkaL commented Nov 30, 2016

Also, mypy could recognize len(x) when x is a variable-length tuple. Example:

def f(x: Tuple[int, ...]) -> None:
    assert len(x) == 3 
    # Type of x should be Tuple[int, int, int] now
    ...

@neiljp
Copy link

neiljp commented Aug 21, 2017

I just ran into this issue and was going to post the following test case, but didn't realise that len(x) wasn't yet a way to distinguish among unions:

def f_4(a: Tuple[int, int, int, int]) -> None:
    pass

def f_2_4(a: Union[Tuple[int, int], Tuple[int, int, int, int]]) -> None:
    if len(a) == 2:
        aa = a + (0,0)
    else:
        aa = a
    f_4(aa)

@JukkaL JukkaL added the false-positive mypy gave an error on correct code label May 18, 2018
srittau added a commit to srittau/typeshed that referenced this issue Aug 17, 2018
python/mypy#1178 is about variable-length tuples, while exc_info()
always returns a tuple with length 3. Ideally, exc_info() would
return Union[Tuple[Type[_E], _E, TracebackType], Tuple[None, None, None]],
but that is a different issue.
JelleZijlstra pushed a commit to python/typeshed that referenced this issue Aug 17, 2018
* Add ExcInfo and OptExcInfo type aliases

* Implement StartResponse using a protocol

* Mark stub-only types with an underscore

* Remove wrong TODO note

python/mypy#1178 is about variable-length tuples, while exc_info()
always returns a tuple with length 3. Ideally, exc_info() would
return Union[Tuple[Type[_E], _E, TracebackType], Tuple[None, None, None]],
but that is a different issue.
yedpodtrzitko pushed a commit to yedpodtrzitko/typeshed that referenced this issue Jan 23, 2019
* Add ExcInfo and OptExcInfo type aliases

* Implement StartResponse using a protocol

* Mark stub-only types with an underscore

* Remove wrong TODO note

python/mypy#1178 is about variable-length tuples, while exc_info()
always returns a tuple with length 3. Ideally, exc_info() would
return Union[Tuple[Type[_E], _E, TracebackType], Tuple[None, None, None]],
but that is a different issue.
@pgunn
Copy link

pgunn commented Apr 4, 2019

I'm getting a different error, but I'm guessing this is the same issue:

#!/usr/bin/env python

from typing import Tuple, Union

####################
# test

def patient_caller():
	a,b,c = annoying_returner(calculate_more=True)
	print(f"I got {a}, {b}, {c}")

def annoying_returner(calculate_more:bool=False) -> Union[Tuple[int,int], Tuple[int,int,int]]:
	if calculate_more:
		return 1,2,3
	else:
		return 4,5

def main():
	patient_caller()

##########

main()
mypytest.py:9: error: Need more than 2 values to unpack (3 expected)

(I have a mypy.ini specifying check_untyped_defs)

@ilevkivskyi
Copy link
Member

@pgunn In your particular case you can use @overload on Literal[True]/Literal[False] for the flag.

@pgunn
Copy link

pgunn commented Apr 4, 2019

@ilevkivskyi Will look into that; unfortunate that it's actually necessary, but it's great that it's possible. Thanks.

@pgunn
Copy link

pgunn commented Apr 4, 2019

@ilevkivskyi I'm trying to follow the docs here:
https://mypy.readthedocs.io/en/latest/literal_types.html
And I'm getting a

caiman/components_evaluation.py:539: error: Overloaded function signatures 1 and 2 overlap with incompatible return types

This is also getting very verbose. Not sure if it's that these are keyword rather than positional arguments that's giving me the problem.

# The below tells the type-checker that if return_all is a literal True, it returns 5 values
@overload
def estimate_components_quality(traces, Y, A, C, b, f, final_frate=30, Npeaks=10, r_values_min=.95,
                                fitness_min=-100, fitness_delta_min=-100, return_all:Literal[True]=True, N=5,
                                remove_baseline=True, dview=None, robust_std=False, Athresh=0.1, thresh_C=0.3, num_traces_per_group=20) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.array]: ...
# and if it's a literal False, it returns two
@overload
def estimate_components_quality(traces, Y, A, C, b, f, final_frate=30, Npeaks=10, r_values_min=.95,
                                fitness_min=-100, fitness_delta_min=-100, return_all:Literal[False]=False, N=5,
                                remove_baseline=True, dview=None, robust_std=False, Athresh=0.1, thresh_C=0.3, num_traces_per_group=20) -> Tuple[np.ndarray, np.ndarray]: ...
# Fallback overload
def estimate_components_quality(traces, Y, A, C, b, f, final_frate=30, Npeaks=10, r_values_min=.95,
                                fitness_min=-100, fitness_delta_min=-100, return_all:bool=False, N=5,
                                remove_baseline=True, dview=None, robust_std=False, Athresh=0.1, thresh_C=0.3, num_traces_per_group=20) -> Union[Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.array], Tuple[np.ndarray, np.ndarray]]: ...

def estimate_components_quality(traces, Y, A, C, b, f, final_frate=30, Npeaks=10, r_values_min=.95,
                                fitness_min=-100, fitness_delta_min=-100, return_all:bool=False, N=5,
                                remove_baseline=True, dview=None, robust_std=False, Athresh=0.1, thresh_C=0.3, num_traces_per_group=20) -> Union[Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.array], Tuple[np.ndarray, np.ndarray]]:
     # Function body

This may be wandering away from the topic of the issue though; if it's wandered too far I'm fine with not getting an answer; don't want to clutter things for devs.

@ilevkivskyi
Copy link
Member

Yes, this is because they are keyword args, see #6580

Anyway, in your case I would just return a less precise type like Tuple[np.ndarray, ...] (if this works).

@pgunn
Copy link

pgunn commented Apr 4, 2019

@ilevkivskyi It does. Thanks! I have some other code not well-covered by this tack, but perhaps by the time we're ready to switch to requiring mypy to pass in CI before merges, this kind of thing will have a more graceful solution.

@goodmami
Copy link
Contributor

There seems to be an issue even with fixed-length tuples:

# file: ex.py
from typing import Tuple
points = (1, 2)  # type: Tuple[int, int]
x, y, z = *points, 0

This results in incorrect inference about the tuple size:

$ mypy ex.py 
ex.py:4: error: Need more than 2 values to unpack (3 expected)
Found 1 error in 1 file (checked 1 source file)

A workaround is to use cast(), as @JukkaL suggested earlier in this thread:

x, y, z = cast(Tuple[int, int, int], (*points, 3))

@ethanhs
Copy link
Collaborator

ethanhs commented Oct 23, 2019

@goodmami could you open a new issue for that? It seems like a different (enough) problem.

@ilevkivskyi
Copy link
Member

I think this is a missing feature rather than a bug (although this does cause a false positive).

@ilevkivskyi ilevkivskyi added feature and removed bug mypy got something wrong labels Aug 3, 2020
@rodo-hf
Copy link

rodo-hf commented Apr 15, 2021

If your list to unpack is never greater than your left side and you know how many variables are on the left side, then you can do this (assuming there are 5 variables on the left side):

list_to_unpack = [1, 2, 3]

a, b, c, d, e = list_to_unpack + (5 - len(list_to_unpack)) * [None]

print(a)
print(b)
print(c)
print(d)
print(e)

# output
# 1
# 2
# 3
# None
# None

if you also want to catch the cases where the list is bigger than the variables, then you can do as follows:

list_to_unpack = [1, 2, 3, 4, 5, 6]
count_variables = 5
a, b, c, d, e = list(list_to_unpack + (count_variables - len(list_to_unpack)) * [None])[:count_variables]

GBeauregard added a commit to GBeauregard/rich that referenced this issue Oct 22, 2021
First of many stabs at eliminating Any and # type: ignore from the
codebase. Here we eliminated several different type ignores; the
remaining # type: ignore is just a mypy limitation.

I also narrowed the typing of RichReprResult from Iterable[Any|...]] to
Iterable[object] since after inspection of the code this is what's
really required.

There's one particularly nasty cast that's needed. It's definitely a
mypy bug that it's not throwing an error for us here. See:
python/mypy#1178 and I've opened a similar
issue for pyright. There's an open PR for mypy that would resolve this,
but at least until this is fixed we need to be casting.

In order to correctly type the auto_repr function, I introduced a
SupportsRichRepr protocol; we could also have made this runtimecheckable
and replaced a hasattr check in the code with it, but it seemed
unnecessary so I opted not to.
@JelleZijlstra JelleZijlstra added the topic-type-narrowing Conditional type narrowing / binder label Mar 19, 2022
@wxgeo
Copy link

wxgeo commented Mar 20, 2023

I ran into a similar issue today.

Minimal example:

transition: tuple[int, int] | tuple[int, int, str] = (1, 1, "a")
if len(transition) == 3: 
    print(transition[2])

An error is raised by mypy for last line: error: Tuple index out of range [misc]

@arvindsree

This comment was marked as off-topic.

@fofoni

This comment was marked as off-topic.

@arvindsree

This comment was marked as off-topic.

@fofoni

This comment was marked as off-topic.

@arvindsree

This comment was marked as off-topic.

@JelleZijlstra
Copy link
Member

This is getting very off-topic. Let's leave this issue focused on narrowing tuple types with len().

@w238liu
Copy link

w238liu commented Aug 25, 2023

Just wondering if this issue has been resolved? I still get a mypy error for the following code snippet:

from typing import Tuple


def f0(n: int) -> Tuple[int, ...]:
    return tuple(range(n))


def f2() -> Tuple[int, int]:
    ret = f0(2)

    assert len(ret) == 2

    return ret


if __name__ == '__main__':
    print(f2())

Save the code in tmp/mypy_test/main.py and run mypy tmp/mypy_test/main.py, I get the following error message:
tmp/mypy_test/main.py:13: error: Incompatible return value type (got "Tuple[int, ...]", expected "Tuple[int, int]") [return-value]

@JelleZijlstra
Copy link
Member

It's not resolved, that's why this issue is still open.

@w238liu
Copy link

w238liu commented Aug 25, 2023 via email

ilevkivskyi added a commit that referenced this issue Oct 21, 2023
Fixes #1178 
Supersedes #10367 

This is includes implementation for fixed length tuples, homogeneous
tuples, and variadic tuples (and combinations of those). Generally
implementation is straightforward. Some notes:
* Unfortunately, it is necessary to add a new attribute `min_len` to
`TypeVarTupleType`, which is probably fine, as it doesn't have that many
attributes so far.
* Supporting more general use cases (like `>` comparisons for variadic
tuples) can cause quick proliferation of unions. I added two mechanisms
to counteract this: not applying the narrowing if the integer literal in
comparison is itself large, and collapsing unions of tuples into a
single tuple (if possible) after we are done with the narrowing. This
looks a bit arbitrary, but I think it is important to have.
* Main missing feature here is probably not inferring type information
from indirect comparisons like `len(x) > foo() > 1`. Supporting this
kind of things in full generality is cumbersome, and we may add cases
that turn out to be important later.
* Note I am quite careful with indexing "inside" a `TypeVarTuple`, it is
not really needed now, but I wanted to make everything future proof, so
that it will be easy to add support for upper bounds for
`TypeVarTuple`s, like `Nums = TypeVarTuple("Nums", bound=tuple[float,
...])`.
* I also fix couple existing inconsistencies with `Any` handling in type
narrowing. It looks like they stem from the old incorrect logic that
meet of `Any` and `X` should be `X`, while in fact it should be `Any`.
These fixes are not strictly necessary, but otherwise there may be new
false positives, because I introduce a bunch of additional type
narrowing scenarios here.

cc @hatal175, thanks for the test cases, and for your nice first attempt
to implement this!
Co-authored-by: Tal Hayon <talhayon1@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
false-positive mypy gave an error on correct code feature priority-1-normal topic-type-narrowing Conditional type narrowing / binder topic-union-types
Projects
None yet