Hone logo
Hone
Problems

Functional Decorators and Higher-Order Functions with functools

This challenge focuses on utilizing the functools module in Python to create powerful and reusable decorators and higher-order functions. Understanding functools is crucial for writing cleaner, more modular, and more Pythonic code, especially when dealing with function composition and memoization. You'll be implementing decorators and functions that leverage functools tools to enhance functionality.

Problem Description

You are tasked with implementing several functions that demonstrate the use of functools in Python. Specifically, you will:

  1. retry decorator: Create a decorator called retry that retries a function a specified number of times if it raises a given exception. The decorator should accept the maximum number of retries and the exception type as arguments.
  2. lru_cache decorator: Implement a function that uses functools.lru_cache to memoize the results of a function. The function should accept the maximum size of the cache as an argument.
  3. compose function: Implement a function called compose that takes a variable number of functions as arguments and returns a single function that is the composition of the input functions. The composition should be performed from right to left (i.e., compose(f, g, h)(x) should be equivalent to h(g(f(x)))).

Key Requirements:

  • The retry decorator must correctly retry the decorated function until it succeeds or the maximum number of retries is reached.
  • The lru_cache function must correctly memoize the results of the decorated function, preventing redundant calculations.
  • The compose function must correctly compose the input functions, applying them in the correct order.
  • All functions should handle edge cases gracefully (e.g., invalid input, empty function lists).

Expected Behavior:

  • The retry decorator should raise the specified exception if the function fails after all retries.
  • The lru_cache function should significantly improve performance for functions with overlapping inputs.
  • The compose function should return a new function that applies the composed functions correctly.

Examples

Example 1: retry decorator

Input:
def my_function():
    import random
    if random.random() < 0.5:
        raise ValueError("Something went wrong")
    return "Success!"

@retry(retries=3, exception=ValueError)
def call_my_function():
    return my_function()

Output: "Success!" (after potentially multiple retries)
Explanation: The `call_my_function` will retry `my_function` up to 3 times if a ValueError is raised.  Eventually, `my_function` will return "Success!" without raising an exception.

Example 2: lru_cache function

Input:
import functools

@lru_cache(maxsize=3)
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(5))
print(fibonacci(4))
print(fibonacci(5))
Output:
5
3
5
Explanation: The `fibonacci` function is memoized with a maximum cache size of 3.  The second call to `fibonacci(5)` retrieves the result from the cache instead of recomputing it.

Example 3: compose function

Input:
def add_one(x):
    return x + 1

def square(x):
    return x * x

def double(x):
    return x * 2

composed_function = compose(add_one, square, double)
result = composed_function(2)

Output: 12
Explanation: `composed_function(2)` is equivalent to `double(square(add_one(2)))`.  `add_one(2)` is 3, `square(3)` is 9, and `double(9)` is 18.

Constraints

  • Retries: The retry decorator must accept a non-negative integer for the number of retries.
  • Cache Size: The lru_cache function must accept a non-negative integer for the cache size.
  • Function Arguments: The compose function can accept any number of functions as arguments.
  • Performance: The lru_cache function should provide a noticeable performance improvement for functions with overlapping inputs.
  • Exception Handling: The retry decorator should raise the specified exception if the function fails after all retries.

Notes

  • The functools module provides several useful tools for functional programming. Explore the documentation for functools.retry, functools.lru_cache, and functools.partial to gain a deeper understanding.
  • Consider using exception handling within the retry decorator to gracefully handle errors.
  • The lru_cache decorator is a powerful tool for optimizing recursive functions and other functions with overlapping inputs.
  • The compose function is a fundamental concept in functional programming, allowing you to combine multiple functions into a single, more complex function.
  • Think about how to handle edge cases, such as empty lists of functions in the compose function. What should the behavior be in these scenarios?
Loading editor...
python