Default arguments are evaluated once, when the function is defined — not on each call. The same list object is reused and accumulates across calls. Use `items=None` and create the list inside the function. This is the single most asked Python gotcha.
nums = [1, 2, 3, 4]
print([n * 2 for n in nums if n % 2 == 0])
Answer: [4, 8]
The `if` filters first, keeping 2 and 4, then the expression doubles them. Reading order is: for, then if, then the expression at the front. A conditional *expression* would go before the `for` instead: `[n*2 if n%2==0 else n for n in nums]`.
`/` always produces a float in Python 3, even when the division is exact. `//` is floor division and `%` gives the remainder. In Python 2 `/` truncated for integers, which is a classic source of migration bugs.
fns = [lambda: i for i in range(3)]
print([f() for f in fns])
Answer: [2, 2, 2]
The lambdas close over the variable `i`, not its value at creation time, and by the time they are called the loop has finished with `i == 2`. Bind it eagerly with a default argument: `lambda i=i: i`. The same trap exists in JavaScript with `var`.
`b = a` binds a second name to the same list object — it does not copy. Mutating through either name is visible from both. Use `a[:]`, `list(a)` or `copy.copy(a)` for an independent shallow copy.
Indexing starts at 0, and negative indices count from the end so `-1` is the last item. Slices are end-exclusive: `[1:3]` takes indices 1 and 2. Slicing never raises IndexError — out-of-range bounds are simply clamped.
1 age = input('Age: ')
2 if age > 18:
3 print('adult')
Answer: Line 2 — input returns a string, which cannot be compared to an int
`input()` always returns a string, and Python refuses to compare str with int rather than guessing. Convert first: `age = int(input('Age: '))`. Wrap that in try/except ValueError if the user might type something non-numeric.
8. This should collect the squares of even numbers but raises a TypeError. Which line is wrong?
Intermediate
python
1 result = []
2 for n in range(10):
3 if n % 2 == 0:
4 result = result.append(n ** 2)
5 print(result)
Answer: Line 4 — append returns None, so result becomes None after the first pass
Methods that mutate in place — `append`, `sort`, `extend`, `update` — return None by convention, precisely so you cannot chain them and mistake them for copies. Just call `result.append(...)` without assigning.
a = [1, 2, 3]
b = a
c = a[:]
a.append(4)
print(len(b), len(c))
Answer: 4 3
`b = a` binds another name to the same list, so it sees the append. `a[:]` creates a shallow copy, which does not. Note the copy is shallow: nested objects are still shared, which is what `copy.deepcopy` is for.
`get` returns None for a missing key, or a supplied default. Square-bracket access raises KeyError instead. Use `get` when absence is expected, and bracket access when a missing key is genuinely a bug you want to hear about.
Answer: Line 3 — assignment rebinds the local name; it does not affect the caller
Python passes object references by value. Mutating the object (line 2) is visible to the caller; rebinding the name (line 3) only changes what the local variable points at. `data` ends up as [1, 2, 3, 4]. Return the new list if you want to replace it.
`range(3)` yields 0, 1, 2 — the stop value is exclusive. `range(1, 4)` would give 1, 2, 3. The `end=' '` argument replaces the default newline, keeping the output on one line.
s = 'hello world'
print(s.split()[1], s[::-1][:5], s.replace('l', 'L', 2))
Answer: world dlrow heLLo world
`split()` on whitespace gives ['hello', 'world']. `[::-1]` reverses the whole string and `[:5]` takes 'dlrow'. `replace` with a count of 2 changes only the first two 'l' characters, leaving the one in 'world' alone.
A generator is a one-shot iterator: once exhausted it stays exhausted. The second `list()` gets nothing. This is why passing a generator to two consumers silently gives the second one an empty sequence — materialise it into a list first if it must be reused.
16. Fill in the blank so the loop iterates over both index and value.
Beginner
python
for i, name in ____(['a', 'b']):
print(i, name)
Answer: enumerate
`enumerate` yields (index, value) pairs, which is cleaner than `for i in range(len(items))` and then indexing. Pass `start=1` if you want human-friendly numbering.
17. Fill in the blank so the file is closed automatically, even if an exception is raised.
Intermediate
python
____ open('data.txt') as f:
content = f.read()
Answer: with
`with` invokes the context-manager protocol, so `__exit__` closes the file whether the block finishes normally or raises. Relying on the garbage collector to close files is unreliable — file descriptors can leak long before it runs.
`sorted()` returns a new sorted list and works on any iterable. `list.sort()` sorts in place and returns None. The naming convention is consistent across Python: functions return new objects, in-place methods return None.
s = 'Python'
print(len(s), s.upper(), s[0], s[-2:])
Answer: 6 PYTHON P on
'Python' has 6 characters, `[0]` is the first, and `[-2:]` takes the last two. String methods always return new strings — `s` itself is never modified, because strings are immutable.
Floats are binary and cannot represent 0.1 exactly, so the sum lands slightly above 0.3. `Decimal` constructed from strings is exact base-10 arithmetic, which is why it is the right choice for money. Note `Decimal(0.1)` from a float would inherit the same error.
21. Which of these safely handle a missing key when counting occurrences? Select all that apply.
Intermediate
python
counts = {}
Answer: counts[k] = counts.get(k, 0) + 1
`get` with a default, `defaultdict(int)` and `Counter` all handle the first occurrence. Plain `counts[k] += 1` reads the key before writing it, so it raises KeyError the first time. `Counter` is the clearest for straight tallying.
`__enter__` and `__exit__` form the context-manager protocol. `__exit__` receives any exception details and can suppress it by returning True. `contextlib.contextmanager` is the lighter alternative for simple cases. `__del__` is not a substitute — its timing is not guaranteed.
A default parameter is used when the caller omits that argument. f-strings interpolate the expressions inside `{}` directly. Note defaults must come after non-default parameters in the signature.
class A:
x = []
a, b = A(), A()
a.x.append(1)
b.x.append(2)
print(a.x, b.x)
Answer: [1, 2] [1, 2]
`x` is a class attribute, so both instances share one list. Mutating through either is visible from both. Per-instance state belongs in `__init__` as `self.x = []`. Note that `a.x = [...]` would create an instance attribute that shadows the class one — assignment and mutation behave differently here.