You run this code:
```python
def make_multipliers():
return [lambda x: i * x for i in range(4)]
print([f(10) for f in make_multipliers()])
```
What is printed, and why?
- A. [0, 10, 20, 30] — each lambda captures its own copy of i at creation time
- B. [30, 30, 30, 30] — all lambdas share i via late binding and see its final value 3 ✓
- C. [0, 0, 0, 0] — i is reset to 0 when the list comprehension exits
- D. TypeError — i is undefined inside the lambda scope
Correct answer: B. Closures capture the variable i by reference, not value, so all four lambdas read i's final value (3) when called, giving 3*10 four times.
A function accumulates data across calls:
```python
def add_item(item, bucket=[]):
bucket.append(item)
return bucket
print(add_item(1))
print(add_item(2))
```
What is printed?
- A. [1] then [2]
- B. [1] then [1, 2] ✓
- C. [1] then [2, 1]
- D. [1, 2] then [1, 2]
Correct answer: B. The default list is created once at function-definition time and shared across every call that omits the argument, so it accumulates: [1] then [1, 2].
Your CPU-bound number-crunching function is slow, so you split it across 4 threads via threading.Thread on CPython 3.11. Wall-clock time barely improves (and sometimes worsens). What is the correct explanation and fix?
- A. Threads block on I/O; switch to asyncio to overlap the waits
- B. The GIL serializes bytecode execution so only one thread runs Python at a time; use multiprocessing to get true parallelism ✓
- C. Thread creation overhead dominates; reuse a ThreadPoolExecutor and it will parallelize
- D. The default recursion limit throttles threads; raise sys.setrecursionlimit and threads will scale
Correct answer: B. Under the GIL only one thread executes Python bytecode at a time, so CPU-bound work doesn't parallelize with threads; multiprocessing (separate interpreters/processes) sidesteps the GIL.
Consider:
```python
a = 256
b = 256
c = 257
d = 257
print(a is b, c is d)
```
Run as a normal script (not line-by-line in a REPL). What is the most reliable output?
- A. True True ✓
- B. True False
- C. False False
- D. False True
Correct answer: A. CPython caches small integers from -5 to 256 so a is b is True; and because c and d are compiled in the same code block, the peephole/const-folding often interns 257 too, so both are True in a script.
You have this class hierarchy:
```python
class A:
def __init__(self): print('A'); super().__init__()
class B(A):
def __init__(self): print('B'); super().__init__()
class C(A):
def __init__(self): print('C'); super().__init__()
class D(B, C):
def __init__(self): print('D'); super().__init__()
D()
```
What is printed?
- A. D B A C A
- B. D B A
- C. D B C A ✓
- D. D B C A C A
Correct answer: C. C3 linearization gives MRO D→B→C→A→object, and cooperative super() walks it exactly once each, so A prints only once after C: D B C A.
A colleague writes a data-only descriptor to validate an attribute but it seems ignored — reads return the instance-dict value directly. Which single fact explains when an instance __dict__ entry shadows a class-level descriptor?
- A. Instance dict always wins over any class attribute, descriptor or not
- B. A non-data descriptor (only __get__) is shadowed by an instance-dict entry, but a data descriptor (defines __set__ or __delete__) takes precedence ✓
- C. Descriptors only work on classes using __slots__
- D. Data descriptors are shadowed by the instance dict; non-data descriptors always win
Correct answer: B. Attribute lookup gives data descriptors (those defining __set__/__delete__) priority over the instance dict, while non-data descriptors are overridden by an instance-dict entry of the same name.
You must call a blocking, thread-unsafe legacy function `legacy_query()` from inside an async coroutine without freezing the event loop. Which approach is correct and safe?
- A. await legacy_query() — awaiting any callable offloads it automatically
- B. result = await asyncio.get_running_loop().run_in_executor(None, legacy_query) ✓
- C. asyncio.create_task(legacy_query()) so it runs concurrently on the loop
- D. Wrap it: result = await asyncio.wait_for(legacy_query(), timeout=5)
Correct answer: B. run_in_executor moves the blocking call onto a worker thread and returns an awaitable, so the event loop keeps running; awaiting a plain blocking function or wrapping a non-coroutine in create_task/wait_for is invalid or still blocks.
This Django view triggers a performance alarm:
```python
for book in Book.objects.all():
print(book.author.name) # author is a ForeignKey
```
With 500 books, roughly how many DB queries run and what is the right fix?
- A. 1 query; already optimal
- B. 501 queries (N+1); fix with Book.objects.select_related('author') ✓
- C. 501 queries (N+1); fix with Book.objects.prefetch_related('author')
- D. 2 queries; fix with .only('author')
Correct answer: B. Each book.author triggers a separate query (1 + 500 = 501, the N+1 problem); select_related does a SQL JOIN for the forward ForeignKey in one query, whereas prefetch_related is for reverse/many-to-many relations.
Predict the output:
```python
def gen():
try:
yield 1
yield 2
finally:
print('cleanup')
g = gen()
print(next(g))
g.close()
print('done')
```
- A. 1, then done (finally never runs because we didn't exhaust it)
- B. 1, then cleanup, then done ✓
- C. 1, then RuntimeError from close()
- D. 1, then done, then cleanup
Correct answer: B. generator.close() raises GeneratorExit at the paused yield, which unwinds through the finally block (printing 'cleanup') before close() returns, then 'done' prints.
You need a shallow-vs-deep-copy-safe 3x3 grid initialized to zeros:
```python
grid = [[0] * 3] * 3
grid[0][0] = 5
print(grid)
```
What is printed?
- A. [[5, 0, 0], [0, 0, 0], [0, 0, 0]]
- B. [[5, 0, 0], [5, 0, 0], [5, 0, 0]] ✓
- C. [[5, 5, 5], [0, 0, 0], [0, 0, 0]]
- D. [[5, 5, 5], [5, 5, 5], [5, 5, 5]]
Correct answer: B. The outer `* 3` replicates the same inner-list reference three times, so mutating grid[0][0] is visible through all three rows.
Why does the Global Interpreter Lock (GIL) limit CPython performance for CPU-bound multithreading?
- A. It prevents any use of threads
- B. It allows only one thread to execute Python bytecode at a time ✓
- C. It disables multiprocessing entirely
- D. It slows down all I/O operations
Correct answer: B. The GIL permits only one thread to run Python bytecode at once, so CPU-bound threads cannot truly run in parallel.
In Python's method resolution order (MRO), what algorithm does new-style class inheritance use?
- A. Depth-first left-to-right
- B. C3 linearization ✓
- C. Breadth-first search
- D. Random ordering
Correct answer: B. Python uses the C3 linearization algorithm to compute a consistent MRO for multiple inheritance.
What does applying @functools.wraps in a decorator accomplish?
- A. It slows the wrapped function
- B. It preserves the wrapped function's __name__ and __doc__ metadata ✓
- C. It prevents the decorator from running
- D. It makes the function immutable
Correct answer: B. functools.wraps copies metadata like __name__ and __doc__ from the original function onto the wrapper.
What is the primary reason dict lookups are O(1) on average in CPython?
- A. Dicts store keys in a sorted array
- B. Dicts use an open-addressing hash table ✓
- C. Dicts use a balanced binary tree
- D. Dicts perform linear scans with caching
Correct answer: B. CPython dicts use an open-addressing hash table, giving average O(1) key access.
In asyncio, what happens if you call a blocking synchronous function directly inside a coroutine?
- A. It runs concurrently automatically
- B. It blocks the entire event loop, stalling other tasks ✓
- C. It raises a RuntimeError immediately
- D. It is automatically offloaded to a thread
Correct answer: B. Blocking calls inside a coroutine freeze the single-threaded event loop, preventing other tasks from progressing.
What does __slots__ accomplish when defined on a class?
- A. It makes all attributes read-only
- B. It prevents inheritance
- C. It restricts attributes and avoids a per-instance __dict__, saving memory ✓
- D. It automatically adds getters and setters
Correct answer: C. __slots__ limits instances to declared attributes and omits __dict__, reducing memory overhead.
Why can a generator's state be resumed, unlike a regular function?
- A. It stores its stack frame and instruction pointer between yields ✓
- B. It restarts from the top each time
- C. It copies its local variables to global scope
- D. It uses a background thread
Correct answer: A. A generator suspends and preserves its frame (locals and position), allowing execution to resume after each yield.
What subtle bug can arise from late binding of closures in a loop, e.g., [lambda: i for i in range(3)]?
- A. All lambdas return 0
- B. All lambdas return the final value of i (2) ✓
- C. It raises a NameError
- D. Each lambda captures a distinct value
Correct answer: B. Closures capture the variable, not its value at creation, so all lambdas see i's final value of 2.
What does a metaclass control in Python?
- A. The behavior of instances only
- B. The creation and behavior of classes themselves ✓
- C. Garbage collection timing
- D. The import order of modules
Correct answer: B. A metaclass is the class of a class, controlling how classes are created and behave.
In CPython, why might sys.getrefcount() return a value higher than expected for an object?
- A. Reference counting is disabled by default
- B. Passing the object as an argument temporarily increments its refcount ✓
- C. It counts weak references too
- D. It always returns a random number
Correct answer: B. Passing the object to getrefcount() itself creates a temporary reference, inflating the reported count.
Why does the Global Interpreter Lock (GIL) limit CPU-bound multithreading in CPython?
- A. It disables all threads entirely
- B. Only one thread executes Python bytecode at a time ✓
- C. It prevents I/O operations from running
- D. It forces all threads onto one CPU core physically
Correct answer: B. The GIL allows only one thread to execute Python bytecode at once, serializing CPU-bound work.
What does the __slots__ attribute in a class primarily achieve?
- A. Enables multiple inheritance
- B. Prevents instance dict creation, reducing memory ✓
- C. Makes attributes read-only
- D. Speeds up method resolution order
Correct answer: B. __slots__ replaces the per-instance __dict__ with fixed storage, saving memory.
In CPython, what does functools.lru_cache use as the cache key by default?
- A. Only positional arguments
- B. The function's id
- C. The arguments (positional and keyword) hashed ✓
- D. The return value
Correct answer: C. lru_cache keys on the hashable call arguments, so all arguments must be hashable.
What is the method resolution order (MRO) algorithm used by Python for new-style classes?
- A. Depth-first left-to-right
- B. C3 linearization ✓
- C. Breadth-first search
- D. Alphabetical by class name
Correct answer: B. Python uses C3 linearization to compute a consistent MRO for multiple inheritance.
Which statement about asyncio is accurate?
- A. It runs coroutines on multiple OS threads in parallel
- B. A single event loop cooperatively schedules coroutines on one thread ✓
- C. await blocks the entire process
- D. Coroutines bypass the GIL for CPU work
Correct answer: B. asyncio uses a single-threaded event loop that cooperatively schedules coroutines at await points.
What does yield from in a generator accomplish?
- A. Returns immediately ending the generator
- B. Delegates iteration to a subgenerator, passing values and sends through ✓
- C. Converts a generator to a list
- D. Raises StopIteration early
Correct answer: B. yield from delegates to a subgenerator, transparently forwarding yielded values, sends, and returns.
Why can modifying a list while iterating over it with a for loop cause skipped elements?
- A. The list is copied each iteration
- B. The internal index advances while indices shift after deletion ✓
- C. Iteration is random-order
- D. Lists cannot be mutated at all
Correct answer: B. Removing an element shifts subsequent items left while the iterator's index still advances, skipping one.
What distinguishes __new__ from __init__ in object creation?
- A. __init__ creates the instance; __new__ initializes it
- B. __new__ creates/returns the instance; __init__ initializes it ✓
- C. They are aliases
- D. __new__ runs only for subclasses
Correct answer: B. __new__ allocates and returns the new instance, then __init__ initializes that instance.
When would a weakref.WeakValueDictionary be preferable to a normal dict for caching?
- A. To keep cached objects alive forever
- B. To allow cached values to be garbage-collected when no other references exist ✓
- C. To make keys immutable
- D. To speed up hashing
Correct answer: B. WeakValueDictionary holds weak references so entries vanish once no strong references remain, preventing leaks.
What is the effect of defining __hash__ = None on a class?
- A. It makes instances hashable with id
- B. It makes instances unhashable, so they can't be dict keys ✓
- C. It caches the hash value
- D. It raises at class definition time
Correct answer: B. Setting __hash__ to None makes instances unhashable, preventing their use as set members or dict keys.