Skip to content

functools

functools.reduce() - Apply function cumulatively to sequence.

functools.partial() - Partially apply function arguments.

functools.lru_cache() - Memoization decorator.

functools.wraps() - Preserve metadata when creating decorators.

functools.total_ordering() - Fill in missing comparison methods.

functools.cmp_to_key() - Convert comparison function to key function.

functools.singledispatch() - Single-dispatch generic functions.

functools.cache() - Unbounded cache (Python 3.9+).

functools.cached_property() - Cached property decorator (Python 3.8+).

Basic: Sum all elements using reduce.

def test_reduce_sum(self):
    """Basic: Sum all elements using reduce."""
    result = functools.reduce(lambda x, y: x + y, [1, 2, 3, 4])
    assert result == 10

Verification: ✅ Tested in CI

Feature: Multiply all elements.

def test_reduce_multiply(self):
    """Feature: Multiply all elements."""
    result = functools.reduce(lambda x, y: x * y, [1, 2, 3, 4])
    assert result == 24

Verification: ✅ Tested in CI

Feature: Provide initial value.

def test_reduce_with_initializer(self):
    """Feature: Provide initial value."""
    result = functools.reduce(lambda x, y: x + y, [1, 2, 3], 10)
    assert result == 16

Verification: ✅ Tested in CI

Edge: Single element returns that element.

def test_reduce_single_element(self):
    """Edge: Single element returns that element."""
    result = functools.reduce(lambda x, y: x + y, [42])
    assert result == 42

Verification: ✅ Tested in CI

Edge: Empty list with initializer returns initializer.

def test_reduce_empty_with_initializer(self):
    """Edge: Empty list with initializer returns initializer."""
    result = functools.reduce(lambda x, y: x + y, [], 0)
    assert result == 0

Verification: ✅ Tested in CI

Error: Empty list without initializer raises TypeError.

def test_reduce_empty_without_initializer_raises(self):
    """Error: Empty list without initializer raises TypeError."""
    with pytest.raises(TypeError):
        functools.reduce(lambda x, y: x + y, [])

Verification: ✅ Tested in CI

Basic: Create partially applied function.

def test_partial_basic(self):
    """Basic: Create partially applied function."""

    def power(base, exponent):
        return base ** exponent
    square = functools.partial(power, exponent=2)
    assert square(5) == 25
    assert square(3) == 9

Verification: ✅ Tested in CI

Feature: Partial with positional arguments.

def test_partial_with_positional(self):
    """Feature: Partial with positional arguments."""

    def multiply(x, y, z):
        return x * y * z
    double = functools.partial(multiply, 2)
    assert double(3, 4) == 24

Verification: ✅ Tested in CI

Edge: Can override partial keywords.

def test_partial_override_keywords(self):
    """Edge: Can override partial keywords."""

    def greet(name, greeting='Hello'):
        return f'{greeting}, {name}!'
    casual_greet = functools.partial(greet, greeting='Hey')
    assert casual_greet('Alice') == 'Hey, Alice!'
    formal = casual_greet('Bob', greeting='Good morning')
    assert formal == 'Good morning, Bob!'

Verification: ✅ Tested in CI

Property: Partial returns callable object.

def test_partial_callable_object(self):
    """Property: Partial returns callable object."""

    def add(x, y):
        return x + y
    add_five = functools.partial(add, 5)
    assert callable(add_five)
    assert add_five(3) == 8

Verification: ✅ Tested in CI

Basic: Cache function results.

def test_lru_cache_basic(self):
    """Basic: Cache function results."""
    call_count = 0

    @functools.lru_cache(maxsize=128)
    def expensive_func(n):
        nonlocal call_count
        call_count += 1
        return n * 2
    result1 = expensive_func(5)
    assert result1 == 10
    assert call_count == 1
    result2 = expensive_func(5)
    assert result2 == 10
    assert call_count == 1

Verification: ✅ Tested in CI

Feature: Different arguments create different cache entries.

def test_lru_cache_different_args(self):
    """Feature: Different arguments create different cache entries."""

    @functools.lru_cache(maxsize=128)
    def square(n):
        return n ** 2
    assert square(3) == 9
    assert square(4) == 16
    assert square(3) == 9

Verification: ✅ Tested in CI

Feature: Cache statistics via cache_info().

def test_lru_cache_info(self):
    """Feature: Cache statistics via cache_info()."""

    @functools.lru_cache(maxsize=128)
    def add(x, y):
        return x + y
    add(1, 2)
    add(1, 2)
    add(2, 3)
    info = add.cache_info()
    assert info.hits == 1
    assert info.misses == 2
    assert info.currsize == 2

Verification: ✅ Tested in CI

Feature: Clear cache with cache_clear().

def test_lru_cache_clear(self):
    """Feature: Clear cache with cache_clear()."""

    @functools.lru_cache(maxsize=128)
    def multiply(x, y):
        return x * y
    multiply(2, 3)
    assert multiply.cache_info().currsize == 1
    multiply.cache_clear()
    assert multiply.cache_info().currsize == 0

Verification: ✅ Tested in CI

Edge: maxsize=None creates unbounded cache.

def test_lru_cache_maxsize_none(self):
    """Edge: maxsize=None creates unbounded cache."""

    @functools.lru_cache(maxsize=None)
    def identity(x):
        return x
    for i in range(1000):
        identity(i)
    info = identity.cache_info()
    assert info.currsize == 1000
    assert info.maxsize is None

Verification: ✅ Tested in CI

Basic: Preserve function metadata in decorators.

def test_wraps_preserves_metadata(self):
    """Basic: Preserve function metadata in decorators."""

    def my_decorator(func):

        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper

    @my_decorator
    def greet(name):
        """Say hello to someone."""
        return f'Hello, {name}!'
    assert greet.__name__ == 'greet'
    assert greet.__doc__ == 'Say hello to someone.'

Verification: ✅ Tested in CI

Edge: Without @wraps, metadata is lost.

def test_wraps_without_decorator(self):
    """Edge: Without @wraps, metadata is lost."""

    def bad_decorator(func):

        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper

    @bad_decorator
    def original():
        """Original docstring."""
        pass
    assert original.__name__ == 'wrapper'
    assert original.__doc__ is None

Verification: ✅ Tested in CI

Basic: Define only eq and lt, get all comparisons.

def test_total_ordering_basic(self):
    """Basic: Define only __eq__ and __lt__, get all comparisons."""

    @functools.total_ordering
    class Number:

        def __init__(self, value):
            self.value = value

        def __eq__(self, other):
            return self.value == other.value

        def __lt__(self, other):
            return self.value < other.value
    a = Number(5)
    b = Number(10)
    assert a < b
    assert a <= b
    assert b > a
    assert b >= a
    assert a != b
    assert a == Number(5)

Verification: ✅ Tested in CI

Basic: Sort with comparison function.

def test_cmp_to_key_basic(self):
    """Basic: Sort with comparison function."""

    def compare(x, y):
        if x > y:
            return -1
        elif x < y:
            return 1
        else:
            return 0
    data = [3, 1, 4, 1, 5, 9, 2, 6]
    sorted_data = sorted(data, key=functools.cmp_to_key(compare))
    assert sorted_data == [9, 6, 5, 4, 3, 2, 1, 1]

Verification: ✅ Tested in CI

Basic: Dispatch based on first argument type.

def test_singledispatch_basic(self):
    """Basic: Dispatch based on first argument type."""

    @functools.singledispatch
    def process(arg):
        return f'generic: {arg}'

    @process.register(int)
    def _(arg):
        return f'int: {arg * 2}'

    @process.register(str)
    def _(arg):
        return f'str: {arg.upper()}'
    assert process(5) == 'int: 10'
    assert process('hello') == 'str: HELLO'
    assert process([1, 2]) == 'generic: [1, 2]'

Verification: ✅ Tested in CI

Feature: Register multiple types at once.

def test_singledispatch_with_types(self):
    """Feature: Register multiple types at once."""

    @functools.singledispatch
    def show(obj):
        return 'unknown'

    @show.register(list)
    @show.register(tuple)
    def _(obj):
        return f'sequence: {len(obj)}'
    assert show([1, 2, 3]) == 'sequence: 3'
    assert show((1, 2)) == 'sequence: 2'

Verification: ✅ Tested in CI

Basic: Simple unbounded cache decorator.

def test_cache_basic(self):
    """Basic: Simple unbounded cache decorator."""
    if not hasattr(functools, 'cache'):
        pytest.skip('cache() requires Python 3.9+')
    call_count = 0

    @functools.cache
    def fibonacci(n):
        nonlocal call_count
        call_count += 1
        if n < 2:
            return n
        return fibonacci(n - 1) + fibonacci(n - 2)
    result = fibonacci(10)
    assert result == 55
    assert call_count == 11

Verification: ✅ Tested in CI

Basic: Property computed only once.

def test_cached_property_basic(self):
    """Basic: Property computed only once."""
    call_count = 0

    class Circle:

        def __init__(self, radius):
            self.radius = radius

        @functools.cached_property
        def area(self):
            nonlocal call_count
            call_count += 1
            return 3.14159 * self.radius ** 2
    c = Circle(5)
    area1 = c.area
    assert call_count == 1
    area2 = c.area
    assert call_count == 1
    assert area1 == area2

Verification: ✅ Tested in CI