Skip to lesson content

PYTHON / LESSON 14 OF 18

Advanced Python

Read the explanations, work through the examples, and complete the student tasks.

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 True

values 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.