Learning goals
Recognize iterators, write a generator and decorator, use a context manager, and measure before optimizing.
Explanation
An iterable can produce an iterator through iter. An iterator supplies items through next and eventually raises StopIteration. A generator function uses yield to pause and later resume, producing values lazily. This can avoid building a large intermediate list, but is not automatically faster for every workload.
A decorator receives a function and returns a replacement, often a wrapper adding behavior. A context manager controls entering and leaving a with block so resources can be cleaned up reliably.
Example 1 Iterators generators and built ins
def squares(limit):
for number in range(limit):
yield number * number
iterator = iter([10, 20])
print(next(iterator), next(iterator))
print(next(iterator, "finished"))
values = squares(4)
print(list(values))
print(list(values))
print(list(enumerate(["Ali", "Sara"], start=1)))
print(list(zip(["Ali", "Sara"], [75, 92])))
print(any([False, True]), all([True, True]))Expected output:
10 20
finished
[0, 1, 4, 9]
[]
[(1, 'Ali'), (2, 'Sara')]
[('Ali', 75), ('Sara', 92)]
True Truevalues is exhausted after the first list conversion. zip normally stops at the shortest input, so validate lengths when unequal input sizes would indicate a data problem.
Example 2 A decorator that preserves the result
from functools import wraps
def announce(function):
@wraps(function)
def wrapper(*args, **kwargs):
print("Starting", function.__name__)
result = function(*args, **kwargs)
print("Finished", function.__name__)
return result
return wrapper
@announce
def add(first, second):
return first + second
print(add(2, 3))Expected output: Starting add, Finished add, then 5. @announce is shorthand for replacing add with announce(add). wraps preserves useful metadata; return result preserves the caller's expected result. This wrapper's Finished message occurs only on success.
Example 3 Context managers
from tempfile import TemporaryDirectory
from pathlib import Path
with TemporaryDirectory() as folder:
path = Path(folder) / "demo.txt"
path.write_text("temporary", encoding="utf-8")
print(path.exists())
print(path.exists())Expected output: True, then False. The temporary directory and file are removed when the block exits.
For performance work, first choose an appropriate data structure, then measure using timeit or cProfile. Set membership is typically average constant time; list membership scans items. The best choice also depends on creation cost and the number of lookups.
Student tasks
1. Write a generator yielding even numbers below 10. Expected values: 0, 2, 4, 6, 8.
2. Create a decorator that counts calls while preserving the wrapped return value.
3. Pair three names with three marks using zip and number them with enumerate.
4. Use a temporary directory, create a file inside it, and verify cleanup after the block.
Completion check: explain why consuming the same generator twice gives different results.