2

I have a function that takes either a list of five ints or five ints as a tuple (*argv).

Here's my function heading: def __init__(self, *argv: Union[int, list]) -> None:

Later in this function I check the contents of this tuple to see if it's a list or five individual ints.

        if type(argv[0]) == int:
            self._l1 = [argv[0], argv[1], argv[2], argv[3], argv[4]]
        else:
            self._l1 = argv[0]

By this point in the code l1 is a list. self._l1 is definitely a list, it's no longer an int, it's a list.

However later in my code when I run this line:

        self._myvar = self._l1.count(1)

I am returned this error from MyPy

Incompatible types in assignment (expression has type "Union[int, List[Any]]", variable has type "List[Union[int, List[Any]]]")

Am I typing this wrong, what do I need to type this as? I've tried so many different types and keep getting errors.

As far as I can tell though my input is a tuple that will either contain a list of ints or five ints. I'd assume it's something like Union[Tuple[List[int]], Tuple[int, ...]] or just Union[List[int], int] or Union[List[int], Tuple[int, ...]], or something similar, but none of these are working for me.

4
  • I would strongly recommend changing your class so that self._l1 is always a list, even if it only contains one item. Try to make one function do many different things just leads to unnecessary complexity. Commented Dec 19, 2021 at 23:47
  • Thanks, but self._l1 is always a list already, it's the argument to the function that can be either a tuple of ints or a tuple containing a single list. Commented Dec 20, 2021 at 0:05
  • Please show the line of input that leads to the error. Not clear whether the problem is when you pass 5 ints, or when you pass a tuple. The error message is a clue: note that it discusses Union. You want to make sure that _li becomes List[int], not a type that still has Union in it. After those 4 lines of code that set _li, add a line that does print(type(self._li)). Commented Dec 20, 2021 at 0:38
  • That's still complicating your function more than necessary: always take a list, and let the caller be responsible for creating the list when necessary. Commented Dec 20, 2021 at 1:11

4 Answers 4

1

Wasn't able to find why you version isn't working (as documentation states it should work for type(...) is syntax, but in my case changing type to if isinstance(argv[0], int): removed your mypy error.

Sign up to request clarification or add additional context in comments.

2 Comments

Documentation is slightly unclear about this, but question is seeing expected behavior. Given that a is X | Y, type(a) is X will narrow a to X within the if. However, it will not narrow a to Y within the else. Documentation only claims the former, yet the question is trying to rely on the latter. isinstance(a, X) however will narrow both a to X within if and to Y within else.
As for why type(a) is X doesn't narrow a to Y in the else, it is because if a is SubclassX then the else will still be chosen, yet issubclass(SubclassX, Y) is False and accessing attributes of Y for a in the else would fail at runtime.
0

I recommend ensuring that _li is always type List[int].

I haven't tested the below code, but cast should work:

if isinstance(argv[0], int):
    self._l1 = [cast(int, argv[0]), cast(int, argv[1]), cast(int, argv[2]), cast(int, argv[3]), cast(int, argv[4])]
else:
    self._l1 = cast(List[int], argv[0])
print(type(self._li))

And where you declare _li and _myvar:

_li: List[int]
_myvar: List[int]

2 Comments

While this will satisfy mypy, it can fail at runtime since argv[0] may be an instance of a subclass of int and not List[int]. See Extending Base Classes. Would instead recommend going with other answer.
@MarioIshac - excellent point - I simply copied that from the question. I've edited my answer to use isinstance. I believe OP is best served by BOTH using isinstance, AND casting down to a simpler type. This proves the input is what it should be, and avoids type problems later on. (In the question, OP was attempting to make _myvar or _li be some complicated Union type.)
0

Why not use typing.overload?

It would look something like this:

from __future__ import annotations
from typing import overload

class MyClass:
    @overload
    def __init__(self: MyClass, a: int, b: int, c: int, d: int, e: int) -> None:
        ...

    @overload
    def __init__(self: MyClass, abcde: tuple[int,int,int,int,int]) -> None:
        ...

    def __init__(self, a, *bcde):
        if bcde:
            b, c, d, e = *bcde
            # etc
        else:
            a, b, c, d, e = *a
            # etc  

Comments

0

So you defined argv as a sequence with the *, which could contain the int values, or a list of the int values. So what you really want to do is "flatten" the sequence.


def flatten(alist):
    """Flatten a nested set of lists into one list."""
    rv = []
    for val in alist:
        if isinstance(val, (list, tuple)):
            rv.extend(flatten(val))
        else:
            rv.append(val)
    return rv

class MyClass:
    def __init__(self, *argv: Union[int, list]):
        # argv is now a tuple of Union[int, list], or List[Union[int, List]], as the compiler says. 
        self._l1 = flatten(argv)
        # Now, you want to verify they are all ints, and that there are five.
        if len(self._l1) != 5:
            raise ValueError("Need five ints")
        if not all(isinstance(o, int) for o in self._l1):
            raise ValueError("not sequence of ints")
        # Now you have a single list of 5 ints.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.