Home > Software engineering >  Type checking Python method with multiple possible inputs
Type checking Python method with multiple possible inputs

Time:12-20

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.

CodePudding user response:

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.

CodePudding user response:

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]

CodePudding user response:

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  

CodePudding user response:

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.

  • Related