# Function decorator that times execution
from time import time
def timer(func):
# Nested wrapper function
def wrapper():
start = time()
func()
end = time()
print(f"Duration: {end-start}")
return wrapper
@timer
def sum_nums():
result = 0
for x in range(1000000):
result += x
sum_nums()
Duration: 0.04120516777038574
from time import time
def timer(func):
# Initialize with extreme values to ensure they get updated correctly
fastest = float('inf') # Initialize with infinity so any time will be faster
slowest = float('-inf') # Initialize with negative infinity so any time will be slower
# The wrapper function now uses nonlocal to access the fastest and slowest variables
@functools.wraps(func)
def wrapper(*args, **kwargs):
nonlocal fastest, slowest
start = time()
result = func(*args, **kwargs)
end = time()
duration = end - start
# Update fastest and slowest times
if duration < fastest:
fastest = duration
if duration > slowest:
slowest = duration
print(f"Duration: {duration:.6f} seconds")
print(f"Fastest: {fastest:.6f} seconds, Slowest: {slowest:.6f} seconds")
return result
return wrapper
# Example usage:
@timer
def sum_nums():
result = 0
for x in range(1000000):
result += x
return result
sum_nums() # Run several times to see the changes in fastest and slowest times
def logger(func):
def wrapper(*args, **kwargs):
print(f"Ran {func.__name__} with args: {args}, and kwargs: {kwargs}")
return func(*args, **kwargs)
return wrapper
@logger
def add(x, y):
return x + y
@logger
def sub(x, y):
return x - y
add(10, 20)
sub(30, 20)
Ran add with args: (10, 20), and kwargs: {}
Ran sub with args: (30, 20), and kwargs: {}
10
import functools
def cache(func):
cache_data = {}
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Sorting kwargs to ensure consistent key structure
sorted_kwargs = tuple(sorted(kwargs.items()))
key = args + sorted_kwargs
if key not in cache_data:
cache_data[key] = func(*args, **kwargs)
return cache_data[key]
return wrapper
import time
@cache
def expensive_func(x):
start_time = time.time()
time.sleep(2)
print(f"{expensive_func.__name__} ran in {time.time() - start_time:.2f} secs")
return x
%time print(expensive_func(1))
expensive_func ran in 2.00 secs
1
CPU times: user 10.4 ms, sys: 2.82 ms, total: 13.2 ms
Wall time: 2 s
%time print(expensive_func(1))
1
CPU times: user 619 µs, sys: 100 µs, total: 719 µs
Wall time: 725 µs
@cache
def fibonacci(n):
if n < 2:
return n
else:
return fibonacci(n-1) + fibonacci(n-2)
fibonacci(10)
55
- Enhance cache with maximum size and LRU cache expiration
import time
from functools import lru_cache
@lru_cache(maxsize=128) # Set maxsize to whatever limit you want
def expensive_func(x):
start_time = time.time()
time.sleep(2)
print(f"{expensive_func.__name__} ran in {time.time() - start_time:.2f} secs")
return x
%time print(expensive_func(1))
expensive_func ran in 2.00 secs
1
CPU times: user 1.17 ms, sys: 4 µs, total: 1.17 ms
Wall time: 2 s
%time print(expensive_func(1))
1
CPU times: user 282 µs, sys: 0 ns, total: 282 µs
Wall time: 240 µs
import time
from functools import wraps
def delay(seconds):
def inner(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Sleeping for {seconds} seconds before running {func.__name__}")
time.sleep(seconds)
return func(*args, **kwargs)
return wrapper
return inner
@delay(seconds=3)
def print_text():
print("Hello World")
print_text()
Sleeping for 3 seconds before running print_text
Hello World
# import time
from functools import wraps
# parameterization so delays can be customized
def delay(seconds):
def inner(func):
@wraps(func)
def wrapper(delay_seconds=seconds, *args, **kwargs):
print(f"Sleeping for {delay_seconds} seconds before running {func.__name__}")
time.sleep(delay_seconds)
return func(*args, **kwargs)
return wrapper
return inner
# import time
from functools import wraps
def delay(seconds):
def inner(func):
@wraps(func)
def wrapper(delay_seconds=seconds, *args, **kwargs):
print(f"Sleeping for {delay_seconds} seconds before running {func.__name__}")
time.sleep(delay_seconds)
return func(*args, **kwargs)
return wrapper
return inner
@delay(seconds=3)
def print_text(text="Hello World"):
print(text)
print_text()
print_text(5, "Hello World 2")
Sleeping for 3 seconds before running print_text
Hello World
Sleeping for 5 seconds before running print_text
Hello World 2
def debug(func):
# Nested wrapper function
def wrapper():
import pdb; pdb.set_trace()
func()
return wrapper
@debug
def sum_nums():
result = 0
for x in range(1000000):
result += x
sum_nums()
> <ipython-input-22-f5d6cfd2b688>(5)wrapper()
-> func()
(Pdb) l
1 def debug(func):
2 # Nested wrapper function
3 def wrapper():
4 import pdb; pdb.set_trace()
5 -> func()
6 return wrapper
[EOF]
(Pdb) c
import time
from functools import wraps
def retry(attempts=3, delay=1):
"""
A decorator for retrying a function if it raises any exception.
:param attempts: The maximum number of attempts to make.
:param delay: The delay (in seconds) between attempts.
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
att = attempts
while att > 0:
try:
return func(*args, **kwargs)
except Exception as e:
att -= 1
if att > 0:
print(f"Retry {attempts - att} of {attempts}: Error occurred: {e}")
time.sleep(delay)
else:
print("All retries failed. Last error: ", str(e))
raise # Re-raise the last exception after final attempt
return wrapper
return decorator
@retry(attempts=5, delay=2)
def test_function():
import random
if random.random() < 0.5:
print("Function succeeded")
return "Success"
else:
print("Function failed")
raise Exception("Simulated failure")
# Call the function and allow any final exceptions to bubble up naturally
for i in range(3):
print(f"Function returned: {test_function()}")
Function failed
Retry 1 of 5: Error occurred: Simulated failure
Function failed
Retry 2 of 5: Error occurred: Simulated failure
Function succeeded
Function returned: Success
Function succeeded
Function returned: Success
Function succeeded
Function returned: Success