How to type annotate reduce function?


I’m writing a very thin wrapper on top of list and I want to define a method called reduce, but I’m struggling to annotate it properly such that pylance, mypy & pylint cut their complaints whenever I use the method, or even define it.

I was perturbed to realize that almost None of Python’s builtin libraries are type annotated.

This is my implementation attempt:

    def reduce(self, func: Callable[[list[T], list[T]], list[T]] = lambda x, y: x + y, default: Optional[T] = None) -> 'List[T]':  # type: ignore
        from functools import reduce
        if default is None: return List(reduce(func, self.list))  # type: ignore
        return List(reduce(func, self.list, default))  # type: ignore

This fails when my List is actually a list of strings

a: List[str] = List(['a', 'b'])
b = a.reduce(lambda x, y: x + y)

Obviously here, the type checkers and linters say they expect list[T] while I passed str.

Asked By: Alex



I think in this situation one would use a generic type, otherwise it can’t reason about your types fully. Heres an example of a list using a Generic type which supports a Protocol that allows it to be reduced:

from functools import reduce
from typing import Sequence, TypeVar, Callable, Generic, Dict, Protocol, cast

A = TypeVar("A")

# define that our type supports the add operator, since it needs to be able to to reduce it
class SupportsAdd(Protocol[A]):
    def __add__(self, __other: A) -> A:

T = TypeVar("T", bound=SupportsAdd)

def _reduce_default(x: T, y: T) -> T:
    return x + y

# instead of using None as the default, we use a special object
# so that we can distinguish between None and a missing default
# (the underlying reduce function does the same thing)
_missing_default = object()

class MyList(Generic[T]):
    def __init__(self, initialdata: Sequence[T]) -> None: = list(initialdata)

    def __getitem__(self, i: int) -> T:

    def __setitem__(self, i: int, v: T) -> None:[i] = v

    def __len__(self) -> int:
        return len(

    def __delitem__(self, i: int) -> None:

    def reduce(
        func: Callable[[T, T], T] = _reduce_default,
        default: T = cast(T, _missing_default),
    ) -> T:
        if default is _missing_default:
            return reduce(_reduce_default,
            return reduce(func,, default)

    # just another method that uses the types
    def add_keys(self, keys: Dict[T, int]) -> None:
        for k in keys:

Some usage of it:

def test() -> None:
    ll: MyList[int] = MyList([1, 2, 3])


    ll[1] = 5


    # add some keys
    ll.add_keys({30: 1, 50: 2, 60: 3})


    val: int  # define the type separately, mypy warns us if it doesnt match
    val = ll.reduce()


    # using strings instead
    ll2: MyList[str] = MyList(["a", "b", "c"])



If you tried to use some class which didn’t support __add__, mypy would warn you:

# test class to see if classes without __add__ fail
class NoAdd:
    def __init__(self, v: int) -> None:
        self.v = v

x: MyList[NoAdd] = [NoAdd(1)] error: Type argument "NoAdd" of "MyList" must be a subtype of "SupportsAdd[Any]"  [type-var] error: Value of type variable "T" of "MyList" cannot be "NoAdd"  [type-var]
Found 2 errors in 1 file (checked 1 source file)
Answered By: Sean Breckenridge
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.