Friday, May 10, 2024

📑 Different Behavior of Functions

📑 Task

1) How will using this decorator affect the function?
2) Can you build your own decorator function that will affect the behavior
of user-defined or built-in functions depending on the arguments?

📑 Answer

1) This gives the function different behavior depending on the type of its argument
2)

📑 Countdown Timers

📑 Task

1) Explain the sequence of actions of this program
2) Can you suggest an alternative code for timing only in Python?

📑 Answer

1) This code renders the countdown timer in the output area
of the Jupyter notebook or IPython environment
It based on Javascript and HTML components, stored in Python strings
2)
import time
from datetime import datetime
def timer(maxiter=100):
    for i in range(maxiter):
        time.sleep(1)
        print(f"🕒 {(59 - datetime.now().second):0>2}", 
              flush=True, end='')
        print("\r", flush=True, end='')
timer()

📑 Memorization Decorators

📑 Task

1) What is the decorator used for in the second function definition?
2) Can you write your own decorator for the same goal?
import functools, time
def fibonacci1(n):
    if n < 2: return n
    return fibonacci1(n - 1) + fibonacci1(n - 2)
# a least-recently-used (LRU) cache eviction policy
@functools.lru_cache(maxsize=None)
def fibonacci2(n):
    if n < 2: return n
    return fibonacci2(n - 1) + fibonacci2(n - 2)
code_str = lambda f, n: f"""
start = time.time(); {f(n)}; stop = time.time() 
print(f'{f}: {{stop - start: e}}')"""
for f in [fibonacci1, fibonacci2]:
    exec(code_str(f, 30))

📑 Answer

1) The memoization provided by `functools.lru_cache` prevents redundant calculations
by storing previously computed Fibonacci numbers and
retrieving them from the cache when needed
2) The decorator with custom cache eviction policies and
different data structures for the cache
def cache(func):
    @functools.wraps(func)
    def wrapper_cache(*args, **kwargs):
        cache_key = args + tuple(kwargs.items())
        if cache_key not in wrapper_cache.cache:
            wrapper_cache.cache[cache_key] = func(*args, **kwargs)
        return wrapper_cache.cache[cache_key]
    wrapper_cache.cache = dict()
    return wrapper_cache
@cache
def fibonacci3(n):
    if n < 2: return n
    return fibonacci3(n - 1) + fibonacci3(n - 2)
for f in [fibonacci1, fibonacci2, fibonacci3]:
    exec(code_str(f, 30))