# functools

### `functools.cache`

A decorator that caches a function's return value each time it is called. If called later with the same arguments, the cached value is returned, and not re-evaluated.

### `functools.lru_cache(maxsize=128)`

A decorator that wraps a function with a memoizing callable that saves up to the maxsize most recent calls.

### `functools.reduce(function, sequence, initial=...)`

Apply a function of two arguments cumulatively to the items of a sequence, from left to right, so as to reduce the sequence to a single value. For example, `functools.reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])` calculates `((((1+2)+3)+4)+5)`. The left argument, `x`, is the accumulated value and the right argument, `y`, is the update value from the sequence. If the optional `initial` is present, it is placed before the items of the sequence in the calculation, and serves as a default when the sequence is empty.

### `functools.partial(f, *args, **kwargs)`

Return a new partial object which when called will behave like `f` called with the positional arguments `args` and keyword arguments `kwargs`. If more arguments are supplied to the call, they are appended to `args`. If additional keyword arguments are supplied, they extend and override `kwargs`.

#### Source code

    class cache:
        def __init__(self, f):
            self.f = f
            self.cache = {}

        def __call__(self, *args):
            if args not in self.cache:
                self.cache[args] = self.f(*args)
            return self.cache[args]

    class lru_cache:
        def __init__(self, maxsize=128):
            self.maxsize = maxsize
            self.cache = {}

        def __call__(self, f):
            def wrapped(*args):
                if args in self.cache:
                    res = self.cache.pop(args)
                    self.cache[args] = res
                    return res

                res = f(*args)
                if len(self.cache) >= self.maxsize:
                    first_key = next(iter(self.cache))
                    self.cache.pop(first_key)
                self.cache[args] = res
                return res
            return wrapped

    def reduce(function, sequence, initial=...):
        it = iter(sequence)
        if initial is ...:
            try:
                value = next(it)
            except StopIteration:
                raise TypeError("reduce() of empty sequence with no initial value")
        else:
            value = initial
        for element in it:
            value = function(value, element)
        return value

    class partial:
        def __init__(self, f, *args, **kwargs):
            self.f = f
            if not callable(f):
                raise TypeError("the first argument must be callable")
            self.args = args
            self.kwargs = kwargs

        def __call__(self, *args, **kwargs):
            kwargs.update(self.kwargs)
            return self.f(*self.args, *args, **kwargs)