3. Functional Patterns
Compose behavior with generators, caching, and higher-order functions for clarity and performance.
Question: 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()
Question: 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
Question: 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)
Question: 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="")