Search code examples
pythontype-hintingmypypython-3.10

mypy Incompatible default for argument


Given the following example

from typing import Any, Iterable, Callable, TypeVar, Tuple

from itertools import islice


T = TypeVar('T')

def take1(n:int, iterable:Iterable[T]) -> Tuple[T,...]:  
    return tuple(islice(iterable, n))
    

def take2(n:int, iterable:Iterable[Any], container:Callable[[Iterable[Any]],T]=tuple) -> T:  
    return container(islice(iterable, n))
    

as is I get

test.py:12: error: Incompatible default for argument "container" (default has type "Type[Tuple[Any, ...]]", argument has type "Callable[[Iterable[Any]], T]")
Found 1 error in 1 file (checked 1 source file)

As you can see take2 is a more generalize version of take1, that by default is take1 but if the user want to put the result into something else there isn't some throw away tuple in the middle by just providing the desired container directly. So for instance "".join(take1(3,"abcdefg")) is equivalent to take2(3,"abcdefg","".join) just that there isn't that throw away tuple that take1 would make, which can be relevant in for example sum(take1(10**10,itertools.count()) vs take2(10**10,itertools.count(),sum) (take1 will fail with a memory error here while take2 will succeed eventually)...

For that I think the hint I put there is perfectly adequate, but mypy doesn't like it.

So, how can I type hint take2 so it pass the mypy test? (beside #type: ignore I suppose) and still get an useful info when calling help on it


Solution

  • after some old trial and error I arrived a solution to make mypy happy

    change the signature to

    def take2(n:int, iterable:Iterable[T], container:Callable[[Iterable[T]],Any]=tuple) -> Any:
        return container(islice(iterable,n))
    

    but that was actually the last solution I found the first was to make a stub file and make that into its own module if it wasn't already

    from typing import TypeVar, overload, Iterable
    
    T = TypeVar("T")
    
    @overload
    def take2(n:int, iterable:Iterable[T]) -> Tuple[T,...]:...
    @overload
    def take2(n:int, iterable:Iterable[Any], container:Callable[[Iterable[Any]],T]) -> T:...
    

    but now with

    @overload
    def take2(n:int, iterable:Iterable[T]) -> Tuple[T,...]:...
    @overload
    def take2(n:int, iterable:Iterable[T], container:Callable[[Iterable[T]],Any]) -> Any:...
    

    (it can also be in the main file)

    mypy sure is hard to please...