Advanced Python Drill and Checkpoint
A review of this module. Skip it if you feel confident; the next module does not need it.
Fix the mistakes
The slips that cost marks in this module. Spot each one, then check the fix.
A real technical round mixes topics: a one-line definition, a buggy snippet, a short program to write. This page revises the five earlier lessons with the bugs interviewers love, a combined example and a mixed question bank. Say the cause first, then the fix, then why.
Iterator and generator traps
Exhausted iterator is an iterator that has already given all its items. Asking it again gives nothing and raises no error, which hides bugs.
The most common generator bug is reading one twice. The first pass uses everything, and the second pass silently finds nothing.
The fix is to build a list when you need two passes.
Example 1 · The exhausted generator bug and two fixes
orders = (amount for amount in [120, 80, 300])
total = sum(orders)count = len(list(orders))print(total, count)
orders = [amount for amount in [120, 80, 300]]print(sum(orders), len(orders))500 0 500 3
What this code does
sum(orders)used the whole generator, solist(orders)is empty and the count is 0, which gives a wrong average later.- A list can be read as often as needed, so the fixed version shows the correct total and count.
Cause: the generator was consumed by sum. Fix: store the items in a list, or create a fresh generator for each pass. Reason: a generator holds one position and never goes back.
Decorator and closure traps
Late binding means a closure looks up a variable when the function runs, not when it is created. Functions made in a loop all see the loop variable's final value.
Two bugs appear again and again. A decorator without functools.wraps hides the real name of the function, and lambdas made in a loop all use the last value.
Both fixes are one line.
Example 2 · A decorator without wraps against one with wraps
from functools import wraps
def plain(func): def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper
def careful(func): @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper
@plaindef pay(): """Take a payment."""
@carefuldef refund(): """Return a payment."""
print(pay.__name__, pay.__doc__)print(refund.__name__, refund.__doc__)wrapper None refund Return a payment.
What this code does
- The plain decorator replaces
paywithwrapper, so the name iswrapperand the docstring is lost. @wraps(func)copies the name and docstring, sorefundkeeps both.
Cause: the wrapper replaced the function, and the lambdas shared one variable. Fix: add @wraps(func) and use a default argument n=n. Reason: the decorator returns a new function object, and closures keep variables, not values.
Concurrency and memory traps
Shared mutable state is data that several threads or names can change. It needs a lock, a thread-safe queue, or a copy to be used safely.
Threads that update one variable need a lock. An asyncio task that blocks freezes the loop. A shallow copy shares inner objects with the original.
An answer should name the tool that fits the job.
Example 3 · A context manager, a generator and a thread pool together
from concurrent.futures import ThreadPoolExecutorfrom contextlib import contextmanager
@contextmanagerdef batch(name): print(f"start {name}") try: yield finally: print(f"end {name}")
def squares(limit): for n in range(limit): yield n * n
with batch("squares"), ThreadPoolExecutor(2) as pool: print(list(pool.map(abs, squares(5))))start squares [0, 1, 4, 9, 16] end squares
What this code does
- The generator
squaresmakes values lazily andpool.maptakes them in order, so the output list is[0, 1, 4, 9, 16]. - The pool is closed first and
batchends last, because managers on one line exit in reverse order.
Quick matches: waiting on a network means threads or asyncio, heavy computation means processes, and an unexpected change through another name means an alias or a shallow copy. The GIL does not protect your data, and a free-threaded build, which is optional, does not either.
Module quiz
Questions from the whole module. Your first answer is your score.
Problems
Graded easy to hard, using everything from this module.
Try each one yourself first. Use the hint if you get stuck. Mark it as done when it works.
Exercise 1· Easy
The generator evens = (n for n in range(10) if n % 2 == 0) is used twice, with sum(evens) and max(evens). Fix it so both results print correctly: 20 and 8.
20 8
Exercise 2· Easy
Write a generator squares_up_to(limit) that yields the squares of 1, 2, 3, … while the square is at most limit. Print list(squares_up_to(50)).
[1, 4, 9, 16, 25, 36, 49]
Exercise 3· Medium
Write a decorator uppercase that upper-cases the string returned by the decorated function, using functools.wraps. Decorate greet(name) returning "hello " + name. Print greet("ravi") and greet.__name__.
HELLO RAVI greet
Exercise 4· Medium
Write a @contextmanager called quiet_errors that catches any ValueError raised in its block, prints handled: <message>, and lets the program continue. Test it with a block that raises ValueError("bad input") and then print continued.
handled: bad input continued
Exercise 5· Medium
Three threads each append their own id (0, 1, 2) to a shared list 2 times under a Lock. After joining, print len(shared) and sorted(set(shared)).
6 [0, 1, 2]
Exercise 6· Hard
Write a memoizing decorator with a dictionary, then use it on a recursive fib(n). Count how many times the function body really runs for fib(20) using a counter dictionary, and print fib(20) and the count.
6765 21
Exercise 7· Hard
Use asyncio to run checkout(1), checkout(2), checkout(3) together, each awaiting asyncio.sleep(0.01 * (4 - i)) and returning f"order {i} paid". Print the list from gather, and print finished first: 3 by recording the completion order in a list and printing the first entry.
['order 1 paid', 'order 2 paid', 'order 3 paid'] finished first: 3
Exercise 8· Hard
Show a leak and its fix with a class Listener. Add one listener to a plain list strong, delete its name and print len(strong) (1). Then add another to a weakref.WeakSet and print its length after deleting the name (0).
1 0
Interview questions
Questions interviewers ask on this topic.
What's next
You finished the preview of Advanced Python Questions
Python Programming has 21 sections, 110 modules and 609 lessons. An account opens every lesson and saves your progress.
Finish up
Mark complete & continue
Next: Output Puzzles: Operators, Division and Truthiness