Python course menu
Intermediate Python Interview Questions and Answers
Intermediate Python interview questions with explanations and code: mutable default arguments, shallow vs deep copy, *args and **kwargs, generators, late-binding closures, scope, class vs instance attributes, and exceptions.
On this page
- 1. What does this print? (Mutable default arguments)
- 2. Shallow copy vs deep copy?
- 3. What are args and kwargs?
- 4. List comprehension vs generator expression?
- 5. What does this print? (Late binding in closures)
- 6. Why does this raise UnboundLocalError?
- 7. Class attributes vs instance attributes: what is the bug?
- 8. What is the difference between sort() and sorted()?
- 9. How do try, except, else and finally work together?
- 10. Which collections module tools do you use?
Intermediate questions test whether you understand how Python behaves, not just its syntax. Many are "predict the output" puzzles built on a single surprising rule. Explain the rule, not just the answer.
1. What does this print? (Mutable default arguments)#
def add(item, items=[]):
items.append(item)
return items
print(add(1))
print(add(2))Show hint
When is the default list created?
Show solution
[1]
[1, 2]Default values are evaluated once, when the function is defined, so every call shares the same list. Fix: items=None, then if items is None: items = [] inside. See Parameters.
2. Shallow copy vs deep copy?#
Show solution
A shallow copy creates a new outer object but shares the inner objects; a deep copy copies everything recursively:
import copy
grid = [[1, 2], [3, 4]]
shallow = grid.copy()
deep = copy.deepcopy(grid)
grid[0][0] = 99
print(shallow[0][0], deep[0][0])99 1The shallow copy still points at the same inner lists, so it sees the change.
3. What are *args and **kwargs?#
Show solution
*args collects extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dict:
def report(*args, **kwargs):
print(args, kwargs)
report(1, 2, unit="kg")(1, 2) {'unit': 'kg'}They are also used to forward arguments, for example in decorators.
4. List comprehension vs generator expression?#
Show solution
A list comprehension builds the whole list in memory; a generator expression produces values lazily, one at a time, and can be consumed only once:
gen = (n * n for n in range(3))
print(list(gen), list(gen))[0, 1, 4] []Use generators for large or one-pass data. See Generators.
5. What does this print? (Late binding in closures)#
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs])Show hint
When does each lambda look up i?
Show solution
[2, 2, 2]Closures look up variables when they are called, not when they are created. By then the loop has finished and i is 2. Capture the current value with a default argument:
funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs])[0, 1, 2]6. Why does this raise UnboundLocalError?#
x = 10
def f():
print(x)
x = 5
f()Show solution
Because x is assigned somewhere in f, Python treats it as a local variable for the whole function. The print(x) then reads the local x before it has a value. Use a different name, pass x as a parameter, or (rarely) declare global x. See Scope.
7. Class attributes vs instance attributes: what is the bug?#
class A:
def __init__(self):
self.items = []
class B:
items = []
a1, a2 = A(), A()
a1.items.append(1)
b1, b2 = B(), B()
b1.items.append(1)
print(a2.items, b2.items)Show solution
[] [1]A creates a new list per object in __init__. B.items is a class attribute, shared by every instance, so appending through b1 also changes what b2 sees. Per-object data belongs in __init__. See Classes.
8. What is the difference between sort() and sorted()?#
Show solution
print([1, 2, 3][::-1], sorted([3, 1, 2]), [3, 1, 2].sort())[3, 2, 1] [1, 2, 3] Nonesorted() returns a new list and works on any iterable. list.sort() sorts in place and returns None, so x = x.sort() loses your list.
9. How do try, except, else and finally work together?#
Show solution
try: code that might fail.except: runs if a matching exception occurs.else: runs only if no exception occurred.finally: always runs, for clean-up.
try:
{}["missing"]
except KeyError as e:
print("caught", repr(e))
finally:
print("finally always runs")caught KeyError('missing')
finally always runsSee Exceptions.
10. Which collections module tools do you use?#
Show solution
Counter: counts items.defaultdict: a dict with an automatic default for missing keys.deque: fast appends and pops at both ends (queues).namedtuple: lightweight records with named fields.
from collections import Counter, defaultdict
print(Counter("mississippi").most_common(2))
groups = defaultdict(list)
for w in ["apple", "avocado", "banana"]:
groups[w[0]].append(w)
print(dict(groups))[('i', 4), ('s', 4)]
{'a': ['apple', 'avocado'], 'b': ['banana']}Next: Advanced Questions.
Finished this lesson?