D3 — Python Internals Quiz
30 minutes. 20 questions. Closed book.
Predict before you run. Several questions ask for program output. Write your prediction first and run nothing until scoring. A question you got right by executing the code measures nothing — the whole point is to measure your model of the runtime, not the runtime.
Mark each answer certain / fairly sure / guessing. Confident-wrong is scored separately from admitted-gap, because they are different defects requiring different fixes.
Table of Contents
- How to Answer
- Section 1: Iterators and Generators
- Section 2: Async and Concurrency
- Section 3: Memory and the Object Model
- Section 4: Data Model and Performance
- Answer Sheet
How to Answer
Two sentences per question is enough. What is being scored is whether you know the
mechanism, not whether you can recite the manual. "It raises TypeError" is half a point;
"it raises TypeError because a just-started generator is suspended before its first yield,
so there is no expression waiting to receive the sent value" is the full point.
Assume CPython on a recent 3.x with the default (GIL-enabled) build unless a question says otherwise.
Section 1: Iterators and Generators
Q1. What happens, and why?
def echo():
while True:
received = yield
print("got", received)
g = echo()
g.send("hello")
Q2. What does list(outer()) produce?
def inner():
yield 1
yield 2
return "done"
def outer():
result = yield from inner()
yield result
Q3. Does cleanup print? Explain the exact mechanism, and name the exception involved.
def gen():
try:
yield 1
yield 2
finally:
print("cleanup")
g = gen()
next(g)
del g
Q4. What does this print, and what is the design defect?
class Countdown:
def __init__(self, n):
self.n = n
def __iter__(self):
while self.n > 0:
yield self.n
self.n -= 1
c = Countdown(3)
print(list(c), list(c))
Q5. What is the value of next(a) on the last line, and why is that surprising?
a = iter([1, 2, 3, 4])
b = [10, 20]
pairs = list(zip(a, b))
next(a)
Q6. What is the difference between an iterable and an iterator? Why does
iter(x) is x hold for one and not the other, and what breaks if you get it wrong?
Q7. You have a generator g that has already yielded three values. You call
g.throw(ValueError("x")). Where does the exception appear from the generator's point of
view, and what are the three possible outcomes?
Q8. Why is itertools.tee(it, 2) a memory hazard? Describe the situation in which it
buffers the entire stream.
Section 2: Async and Concurrency
Q9. One of these two tasks raises. What happens to the other one?
async def boom():
raise ValueError("boom")
async def slow():
await asyncio.sleep(10)
print("slow finished")
await asyncio.gather(boom(), slow())
Then: how does asyncio.TaskGroup differ, and what exception type does it raise?
Q10. asyncio.CancelledError — what does it inherit from, and what is the practical
consequence for code that writes except Exception: inside a coroutine?
Q11. What is wrong with this, and what is the standard fix?
async def main():
for url in urls:
asyncio.create_task(fetch(url))
await asyncio.sleep(5)
Name two independent defects.
Q12. A coroutine calls time.sleep(2) (not asyncio.sleep). Describe precisely what
happens to the event loop and to every other pending task. Then name the correct escape hatch
for genuinely blocking work.
Q13. What exactly does the GIL guarantee, and what does it not? Is counter += 1 on a
shared integer thread-safe? Is some_list.append(x)? Explain the difference in terms of
bytecode.
Q14. What is the current status of free-threaded CPython? Name the relevant PEPs, the version in which the status changed, whether it is the default build, and roughly what the single-threaded overhead is.
Q15. Give the decision rule for threads vs. processes vs. asyncio. For each, state the workload it wins on and the specific cost that makes it lose elsewhere.
Section 3: Memory and the Object Model
Q16. Does __del__ run for objects in a reference cycle? What changed, and in which
Python version? What is the remaining hazard?
class Node:
def __init__(self): self.ref = None
def __del__(self): print("__del__ ran")
x = Node(); y = Node()
x.ref = y; y.ref = x
del x, y
Q17. What does __slots__ actually remove, what does it break, and what happens to the
memory saving when a subclass of a slotted class does not itself declare __slots__?
Q18. Why is sys.getsizeof(some_list_of_10000_strings) misleading? What does it measure,
and what would you use instead to answer "how much memory is this actually costing me?"
Section 4: Data Model and Performance
Q19. Attribute lookup precedence: rank these four in the order CPython consults them for
instance.x — instance __dict__, data descriptor on the type, non-data descriptor on the
type, __getattr__. Then explain why @property can shadow an instance attribute but a
plain function cannot.
Q20. __getattr__ vs __getattribute__ — when is each called, which one is the
performance hazard, and what is the classic infinite-recursion bug in __getattribute__?
Answer Sheet
Copy this, fill it in, and score against ANSWER-KEY.md.
| Q | Answer (2 sentences) | Confidence | Correct? |
|---|---|---|---|
| 1 | |||
| 2 | |||
| 3 | |||
| 4 | |||
| 5 | |||
| 6 | |||
| 7 | |||
| 8 | |||
| 9 | |||
| 10 | |||
| 11 | |||
| 12 | |||
| 13 | |||
| 14 | |||
| 15 | |||
| 16 | |||
| 17 | |||
| 18 | |||
| 19 | |||
| 20 |
Correct: ___ / 20 Confident-wrong (marked certain but wrong): ___
The second number is the one that matters most. A gap you know about is a study item. A gap
you are confident about is a landmine — it is the thing you will assert in an interview and
be corrected on, and the correction costs far more than the admission would have. Every
confident-wrong answer goes straight into the review/ spaced-repetition
queue at the 1-day interval.