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:
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:
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:
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:
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:
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:
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) —
887 wordsLast 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