3. Functional Patterns
Compose behavior with generators, caching, and higher-order functions for clarity and performance.
Q1 How can you create a context manager besides using a class?
Answer: The @contextlib.contextmanager decorator allows you to create a context manager from a simple generator function. This is often more concise than writing a full class with __enter__ and __exit__.
Explanation: The code before the yield statement acts as the setup (__enter__), the yield provides the resource, and the code in a finally block after the yield serves as the cleanup (__exit__), guaranteeing resource release even if an exception occurs. For managing a dynamic number of contexts, contextlib.ExitStack is a powerful utility.
from contextlib import contextmanager
@contextmanager
def open_readonly(path):
f = open(path, "rb")
try:
yield f
finally:
f.close()
Q2 What is yield from and when is it useful?
Answer: yield from delegates part of a generator’s operations to a subgenerator, simplifying generator composition and automatically forwarding .send(), .throw(), and .close().
Explanation: It reduces boilerplate when building pipelines.
def read_lines(paths):
for p in paths:
with open(p) as f:
yield from f # delegate iteration
Q3 When should you use functools.lru_cache?
Answer: Use it to memoize pure functions where inputs fully determine outputs and cache size is bounded.
Explanation: It trades memory for speed and requires hashable arguments.
from functools import lru_cache
@lru_cache(maxsize=1024)
def parse_date(s: str) -> datetime:
return expensive_parse(s)
Q4 How does partial application help in Python?
Answer: functools.partial pre-binds some arguments, producing simpler call sites and aiding higher-order APIs.
Explanation: It’s useful in callbacks and pipelines.
from functools import partial
strip_commas = partial(str.replace, old=",", new="")