Teaching Kids Programming: Videos on Data Structures and Algorithms
Introduction to the Function Decorator in Python
In Python, we can add decorate a function by using the @ symbol, following by the decorator. For example:
1 2 3 4 5 6 7 8 9 10 | def sample_decorator(func): def wrapper(n): print("Before") ans = func(n) print("After") return wrapper @sample_decorator def say_hello(s): print(f"Hello {s}") |
def sample_decorator(func): def wrapper(n): print("Before") ans = func(n) print("After") return wrapper @sample_decorator def say_hello(s): print(f"Hello {s}")
Then we call say_hello(“World”), then we can see the following output:
1 2 3 | Before Hello World After |
Before Hello World After
The function decorator takes a single parameter which is the function, and the function decorator returns a wrapper function. We can use the (*args, **kwargs) which is a Python syntax used to define functions that can accept any number of positional and keyword arguments.
*args: This syntax collects any number of positional arguments into a tuple named args. When a function is called with positional arguments, they are collected into a tuple. Inside the function, you can access these arguments using the args tuple.
**kwargs: This syntax collects any number of keyword arguments into a dictionary named kwargs. When a function is called with keyword arguments, they are collected into a dictionary. Inside the function, you can access these arguments using the kwargs dictionary.
Here’s an example to illustrate how *args and **kwargs work:
1 2 3 4 5 6 | def example_func(*args, **kwargs): print("Positional arguments (args):", args) print("Keyword arguments (kwargs):", kwargs) # Calling the function with different arguments example_func(1, 2, 3, a='apple', b='banana', c='cherry') |
def example_func(*args, **kwargs): print("Positional arguments (args):", args) print("Keyword arguments (kwargs):", kwargs) # Calling the function with different arguments example_func(1, 2, 3, a='apple', b='banana', c='cherry')
Output:
1 2 | Positional arguments (args): (1, 2, 3) Keyword arguments (kwargs): {'a': 'apple', 'b': 'banana', 'c': 'cherry'} |
Positional arguments (args): (1, 2, 3) Keyword arguments (kwargs): {'a': 'apple', 'b': 'banana', 'c': 'cherry'}
In this example, args contains the positional arguments (1, 2, 3), and kwargs contains the keyword arguments {‘a’: ‘apple’, ‘b’: ‘banana’, ‘c’: ‘cherry’}. This syntax allows you to create flexible functions that can handle various types and numbers of arguments.
The @cache Function Decorator
The @cache decorator can be implemented using Python’s built-in functools module, specifically the lru_cache decorator. This decorator caches the results of a function call and returns the cached result when the same inputs occur again.
Here’s an implementation of the @cache decorator using functools.lru_cache to solve the Fibonacci Numbers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | from functools import lru_cache def cache(func): # Using functools.lru_cache to cache function results cached_func = lru_cache()(func) def wrapper(*args, **kwargs): return cached_func(*args, **kwargs) # Copying attributes of the original function wrapper.__name__ = func.__name__ wrapper.__doc__ = func.__doc__ wrapper.__module__ = func.__module__ return wrapper # Example usage: @cache def fibonacci(n): if n <= 1: return n else: return fibonacci(n-1) + fibonacci(n-2) # Test the fibonacci function with caching print(fibonacci(10)) # This call and further calls with the same input will be cached |
from functools import lru_cache def cache(func): # Using functools.lru_cache to cache function results cached_func = lru_cache()(func) def wrapper(*args, **kwargs): return cached_func(*args, **kwargs) # Copying attributes of the original function wrapper.__name__ = func.__name__ wrapper.__doc__ = func.__doc__ wrapper.__module__ = func.__module__ return wrapper # Example usage: @cache def fibonacci(n): if n <= 1: return n else: return fibonacci(n-1) + fibonacci(n-2) # Test the fibonacci function with caching print(fibonacci(10)) # This call and further calls with the same input will be cached
In this implementation:
- lru_cache() is used to create a caching mechanism.
- The inner wrapper function serves as a wrapper for the original function func, allowing for customization.
- The attributes of the original function, such as __name__, __doc__, and __module__, are copied to the wrapper function to preserve metadata.
- The decorator can be applied to any function that needs caching.
- This @cache decorator will memoize the results of function calls, significantly improving performance for repeated calls with the same arguments.
We can implement a basic cache decorator without using lru_cache. Here’s a simple implementation using a Hash Map aka Dictionary:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | def cache(func): cache_dict = {} def wrapper(*args, **kwargs): key = (args, frozenset(kwargs.items())) if key not in cache_dict: cache_dict[key] = func(*args, **kwargs) return cache_dict[key] return wrapper # Example usage: @cache def fibonacci(n): if n <= 1: return n return fibonacci(n-1) + fibonacci(n-2) # Test the fibonacci function with caching print(fibonacci(10)) # This call and further calls with the same input will be cached |
def cache(func): cache_dict = {} def wrapper(*args, **kwargs): key = (args, frozenset(kwargs.items())) if key not in cache_dict: cache_dict[key] = func(*args, **kwargs) return cache_dict[key] return wrapper # Example usage: @cache def fibonacci(n): if n <= 1: return n return fibonacci(n-1) + fibonacci(n-2) # Test the fibonacci function with caching print(fibonacci(10)) # This call and further calls with the same input will be cached
In this implementation:
- cache_dict is used as an in-memory dictionary to store the results of function calls.
- The wrapper function checks if the arguments and keyword arguments have been seen before. If not, it calls the original function and stores the result in the cache dictionary.
- The cached result is returned if the same arguments and keyword arguments are provided again.
- This implementation is a basic form of caching and does not provide features like eviction policies or a maximum cache size, which are available in lru_cache.
–EOF (The Ultimate Computing & Technology Blog) —
loading...
Last Post: Implement and Test the Go Version of Redis Caching Class
Next Post: Teaching Kids Programming - Using Hash Map to Count the Right Triangles in a Binary Matrix