python
exercises
exercises.py🐍python
"""
10 - Advanced Functions: Exercises
Practice closures, decorators, and generators.
"""
print("=" * 60)
print("ADVANCED FUNCTIONS EXERCISES")
print("=" * 60)
# =============================================================================
# EXERCISE 1: Closure - Make Accumulator
# Create a function make_accumulator() that returns a function
# which accumulates values added to it.
# =============================================================================
print("\n--- Exercise 1: Accumulator ---")
# Your code here:
# Test:
# acc = make_accumulator()
# print(acc(10)) # 10
# print(acc(5)) # 15
# print(acc(20)) # 35
# =============================================================================
# EXERCISE 2: Simple Decorator
# Create a decorator 'log_call' that prints when a function
# is called and what arguments it received.
# =============================================================================
print("\n--- Exercise 2: Log Decorator ---")
# Your code here:
# Test:
# @log_call
# def add(a, b):
# return a + b
# result = add(3, 5)
# Should print: "Calling add with args=(3, 5), kwargs={}"
# =============================================================================
# EXERCISE 3: Decorator with Arguments
# Create a decorator 'retry' that retries a function
# a specified number of times if it raises an exception.
# =============================================================================
print("\n--- Exercise 3: Retry Decorator ---")
from functools import wraps
# Your code here:
# Test:
# attempt = 0
# @retry(max_attempts=3)
# def flaky_function():
# global attempt
# attempt += 1
# if attempt < 3:
# raise ValueError("Not yet!")
# return "Success!"
# print(flaky_function())
# =============================================================================
# EXERCISE 4: Basic Generator
# Create a generator 'countdown' that counts down from n to 1.
# =============================================================================
print("\n--- Exercise 4: Countdown Generator ---")
# Your code here:
# Test:
# for num in countdown(5):
# print(num, end=" ")
# Should print: 5 4 3 2 1
# =============================================================================
# EXERCISE 5: Generator - Even Numbers
# Create a generator that yields even numbers up to n.
# =============================================================================
print("\n--- Exercise 5: Even Numbers Generator ---")
# Your code here:
# Test:
# print(list(even_numbers(10)))
# Should print: [0, 2, 4, 6, 8, 10]
# =============================================================================
# EXERCISE 6: Generator Expression
# Using a generator expression, create a generator that
# yields the squares of numbers from 1 to n that are divisible by 3.
# =============================================================================
print("\n--- Exercise 6: Generator Expression ---")
# Your code here:
# Test:
# print(list(squares_div_by_3(20)))
# Should print: [9, 36, 81, 144, 225, 324]
# =============================================================================
# EXERCISE 7: Iterator Class
# Create a RangeReverse class that iterates backwards
# from start to end (inclusive).
# =============================================================================
print("\n--- Exercise 7: Reverse Range Iterator ---")
# Your code here:
# Test:
# for num in RangeReverse(10, 5):
# print(num, end=" ")
# Should print: 10 9 8 7 6 5
# =============================================================================
# EXERCISE 8: Memoization Decorator
# Create a memoization decorator that caches function results.
# Track cache hits and misses.
# =============================================================================
print("\n--- Exercise 8: Memoization ---")
# Your code here:
# Test:
# @memoize
# def fib(n):
# if n < 2:
# return n
# return fib(n-1) + fib(n-2)
# print(fib(30))
# print(f"Cache hits: {fib.cache_hits}")
# =============================================================================
# EXERCISE 9: Higher-Order Function
# Create a function 'pipe' that takes multiple functions and
# returns a new function that applies them left to right.
# =============================================================================
print("\n--- Exercise 9: Pipe Function ---")
# Your code here:
# Test:
# add_one = lambda x: x + 1
# double = lambda x: x * 2
# square = lambda x: x ** 2
# piped = pipe(add_one, double, square)
# print(piped(3)) # ((3 + 1) * 2) ^ 2 = 64
# =============================================================================
# EXERCISE 10: File Reader Generator
# Create a generator that reads a file line by line
# and yields each line stripped of whitespace.
# =============================================================================
print("\n--- Exercise 10: File Reader ---")
# Your code here:
# Test (create a test file first):
# for line in read_lines("test.txt"):
# print(line)
# =============================================================================
# SOLUTIONS
# =============================================================================
print("\n\n" + "=" * 60)
print("SOLUTIONS")
print("=" * 60)
# SOLUTION 1
print("\n--- Solution 1: Accumulator ---")
def make_accumulator():
total = 0
def accumulator(value):
nonlocal total
total += value
return total
return accumulator
acc = make_accumulator()
print(f"acc(10): {acc(10)}")
print(f"acc(5): {acc(5)}")
print(f"acc(20): {acc(20)}")
# SOLUTION 2
print("\n--- Solution 2: Log Decorator ---")
def log_call(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result}")
return result
return wrapper
@log_call
def add(a, b):
return a + b
result = add(3, 5)
# SOLUTION 3
print("\n--- Solution 3: Retry Decorator ---")
def retry(max_attempts=3):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"Attempt {attempt + 1} failed: {e}")
if attempt == max_attempts - 1:
raise
return wrapper
return decorator
attempt_count = 0
@retry(max_attempts=3)
def flaky_function():
global attempt_count
attempt_count += 1
if attempt_count < 3:
raise ValueError("Not yet!")
return "Success!"
print(flaky_function())
# SOLUTION 4
print("\n--- Solution 4: Countdown ---")
def countdown(n):
while n >= 1:
yield n
n -= 1
print("Countdown from 5:")
for num in countdown(5):
print(num, end=" ")
print()
# SOLUTION 5
print("\n--- Solution 5: Even Numbers ---")
def even_numbers(n):
for i in range(0, n + 1, 2):
yield i
print(f"Even numbers up to 10: {list(even_numbers(10))}")
# SOLUTION 6
print("\n--- Solution 6: Generator Expression ---")
def squares_div_by_3(n):
return (x**2 for x in range(1, n + 1) if x % 3 == 0)
print(f"Squares divisible by 3 (up to 20): {list(squares_div_by_3(20))}")
# SOLUTION 7
print("\n--- Solution 7: Reverse Range ---")
class RangeReverse:
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.current < self.end:
raise StopIteration
result = self.current
self.current -= 1
return result
print("Reverse range 10 to 5:")
for num in RangeReverse(10, 5):
print(num, end=" ")
print()
# SOLUTION 8
print("\n--- Solution 8: Memoization ---")
def memoize_adv(func):
cache = {}
hits = 0
misses = 0
@wraps(func)
def wrapper(*args):
nonlocal hits, misses
if args in cache:
hits += 1
return cache[args]
misses += 1
result = func(*args)
cache[args] = result
return result
wrapper.cache_hits = property(lambda self: hits) # type: ignore[attr-defined]
wrapper.cache_misses = property(lambda self: misses) # type: ignore[attr-defined]
wrapper.get_stats = lambda: (hits, misses) # type: ignore[attr-defined]
return wrapper
@memoize_adv
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
print(f"fib(30) = {fib(30)}")
print(f"Cache stats (hits, misses): {fib.get_stats()}")
# SOLUTION 9
print("\n--- Solution 9: Pipe Function ---")
def pipe(*functions):
def piped(arg):
result = arg
for f in functions:
result = f(result)
return result
return piped
add_one = lambda x: x + 1
double = lambda x: x * 2
square = lambda x: x ** 2
piped = pipe(add_one, double, square)
print(f"pipe(add_one, double, square)(3) = {piped(3)}")
print("Steps: 3 -> 4 -> 8 -> 64")
# SOLUTION 10
print("\n--- Solution 10: File Reader ---")
def read_lines(filepath):
try:
with open(filepath, 'r') as f:
for line in f:
yield line.strip()
except FileNotFoundError:
print(f"File {filepath} not found")
# Create a test file and read it
test_content = """Line 1
Line 2
Line 3"""
with open("/tmp/test_lines.txt", "w") as f:
f.write(test_content)
print("Reading test file:")
for line in read_lines("/tmp/test_lines.txt"):
print(f" {line}")
print("\n" + "=" * 60)
print("END OF EXERCISES")
print("=" * 60)