Exercise 1 β Cache Decorator
def cache(func):
memo = {}
def wrapper(*args):
if args not in memo:
memo[args] = func(*args)
return memo[args]
return wrapper
@cache
def expensive(n):
print(f"Computing {n}...")
return n * n
expensive(5) # Computing 5... β 25
expensive(5) # from cache β 25Exercise 2 β Stack Class
class Stack:
def __init__(self):
self._items = []
def push(self, item): self._items.append(item)
def pop(self):
if not self._items:
raise IndexError("Stack is empty")
return self._items.pop()
def peek(self):
if not self._items:
raise IndexError("Stack is empty")
return self._items[-1]
def __len__(self): return len(self._items)
def __bool__(self): return bool(self._items)
s = Stack()
s.push(1); s.push(2); s.push(3)
print(s.peek()) # 3
print(s.pop()) # 3Exercise 3 β Infinite Counter Generator
def counter(start=0, step=1):
n = start
while True:
yield n
n += step
import itertools
evens = list(itertools.islice(counter(0, 2), 5))
print(evens) # [0, 2, 4, 6, 8]Exercise 4 β Timer Context Manager
import time
from contextlib import contextmanager
@contextmanager
def timer(label=""):
start = time.perf_counter()
try:
yield
finally:
elapsed = time.perf_counter() - start
print(f"{label}: {elapsed:.4f}s")
with timer("List comp"):
result = [x**2 for x in range(1_000_000)]Exercise 5 β Flatten Nested List
from collections.abc import Iterable
def flatten(iterable):
for item in iterable:
if isinstance(item, Iterable) and not isinstance(item, str):
yield from flatten(item)
else:
yield item
nested = [1, [2, [3, 4]], [5, 6], 7]
print(list(flatten(nested))) # [1, 2, 3, 4, 5, 6, 7]ποΈ Practical Exercise
Warm up with focused drills:
- Write a memoizing cache decorator from scratch (then compare with
functools.lru_cache). - Implement a
Stackclass withpush,pop,peek, andis_empty. - Write a generator that yields an infinite counter.
- Flatten an arbitrarily nested list using recursion.
π₯ Challenge Exercise
Combine the building blocks: implement a Timer context manager, then use your Stack class and the flatten function together in a small program. Add type hints and a few tests for each component. For the cache decorator, support a configurable max size and demonstrate the speedup on a slow function. Bonus: re-implement the infinite counter both as a generator and as a class with __next__.
π Summary
- These exercises drill core Python skills used in interviews and real code.
- A cache decorator demonstrates closures and decorators;
functools.lru_cachedoes it for you. - A Stack class practices encapsulation and data-structure design.
- Generators (
yield) produce values lazily, including infinite sequences. - A Timer context manager applies the
withprotocol. - Flattening a nested list reinforces recursion.
Interview Questions on Python Coding Exercises
- How do you write a decorator that caches function results?
- How would you implement a stack in Python?
- What is the difference between a generator and a regular function?
- How do you build a context manager?
- How do you flatten a nested list recursively?
- When would you use
functools.lru_cache? - How do these exercises map to real-world Python features?
Related Topics
FAQ
lru_cache exists? +Building one yourself cements your understanding of closures, decorators, and how memoization works. In real code you would usually reach for functools.lru_cache, but interviewers often ask you to implement the concept.
A regular function returns once. A generator uses yield to produce a sequence of values lazily, pausing and resuming its state between calls β perfect for streams and infinite sequences without using extra memory.
A Python list already supports stack behavior with append (push) and pop. Wrapping it in a class with push, pop, peek, and is_empty gives a clean, explicit interface.
Recurse: iterate the list, and for each element, if it is itself a list, recurse into it and extend the result; otherwise append the value. This handles arbitrary nesting depth.
