python

exercises

exercises.py🐍
"""
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)
Exercises - Python Tutorial | DeepML