1.3.2 · D5Python Intermediate
Question bank — Standard library — math, random, os, sys, datetime, time, collections, itertools, functools
Before you start, three plain-word reminders the traps lean on:
- An iterator is a thing you can pull items from one at a time; once pulled, an item is gone forever (it does not rewind).
- Inclusive means "the endpoint is included"; exclusive means "stops just before it".
- A factory is a function you hand over so something else can call it later to build fresh values.
True or false — justify
TF — math.comb(n, k) can return a float for large inputs.
False. It works in exact integers the whole way, so
math.comb(100, 50) is a precise big integer with zero floating-point error, unlike dividing factorials by hand.TF — random.randint(1, 6) and random.randrange(1, 6) roll the same set of values.
False.
randint(1,6) is inclusive on both ends (1–6, a real die), while randrange(1,6) excludes the top (1–5), mirroring range.TF — Setting random.seed(42) makes the numbers non-random.
Roughly true and it is a feature: the same seed always reproduces the same sequence, which makes tests repeatable — the numbers still look random, they are just deterministic.
TF — os.path.join('data', 'file.csv') always inserts a forward slash.
False. It inserts the OS-native separator:
/ on Linux/macOS, \ on Windows. That portability is the entire reason to prefer it over string concatenation.TF — time.time() is the best clock for measuring how long a function takes.
False. Use
time.perf_counter(): it is the highest-resolution monotonic clock and never jumps backward, whereas time.time() can leap if the system clock is adjusted mid-measurement.TF — A namedtuple lets you change a field after creation.
False. It is still a tuple, so it is immutable; you get readable named access but not mutation. Use
_replace to get a new copy with a changed field.TF — itertools.combinations('ABC', 2) gives more results than permutations('ABC', 2).
False. Combinations ignore order, so there are fewer (3: AB, AC, BC) versus permutations' 6 — permutations counts each pair's two orderings separately.
TF — functools.reduce(f, [x1, x2, x3]) applies f right-to-left.
False. It folds left-to-right:
f(f(x1, x2), x3). The accumulator starts on the left and each new element joins from the right.TF — lru_cache speeds up every function you decorate with it.
False. It only helps functions that (a) are called repeatedly with the same arguments and (b) return the same output for those arguments (pure). Cache a function with side effects or unique args and you gain nothing (or hide bugs).
Spot the error
Error? d = defaultdict([]) then d['x'].append(1).
Yes.
defaultdict wants the factory itself — pass list, not list() / []. It calls what you gave it for each missing key; a bare [] is not callable, so this raises immediately.Error? first = combinations('ABC', 2)[0].
Yes.
combinations returns a one-shot iterator, not a list, and iterators are not indexable. Wrap it: list(combinations('ABC', 2))[0].Error? Loop over combinations(...) twice expecting the same items both times.
Yes. The first loop exhausts the iterator; the second loop sees nothing. Materialise it once with
list(...) if you need to reuse it.Error? now = datetime.now(); later = now + 1.
Yes. Adding a bare
1 to a datetime is ambiguous (1 what?) and raises a TypeError. Use an explicit unit: now + timedelta(days=1).Error? datetime.strftime('2024-01-31', '%Y-%m-%d') to parse a string.
Yes, swapped.
strftime formats an object to a string; strptime parses a string into an object. Here you want strptime.Error? math.sqrt(1e200**2 + 1e200**2) to get the hypotenuse of huge legs.
Yes, risky. Squaring
1e200 overflows to infinity before the sqrt. math.hypot(1e200, 1e200) scales internally and returns the right value safely.Error? Using a plain list as a queue with mylist.pop(0) in a hot loop.
Not a crash, but a performance error. Popping from the front of a list is (everything shifts left). Use
collections.deque and popleft() for .Error? Counter([1,2,2,3]).most_common()[0] to get the least common item.
Yes, backwards.
most_common() sorts from most frequent down, so [0] is the most common. For the least, take most_common()[-1].Why questions
Why does argv[0] hold the script name rather than the first real argument?
Convention inherited from C: the running program is passed its own name first, so the actual user arguments start at
argv[1].Why is math the wrong tool for adding two lists of numbers element-wise?
math functions operate on single numbers, not arrays. Element-wise array math is NumPy's job; math.sqrt([1,4]) would just error.Why can you loop over itertools.count(0) without running out of memory?
It is lazy — an iterator that produces the next number only when you ask. Nothing is stored ahead of time, so an "infinite" sequence costs constant memory (just remember to
break).Why does random.sample(range(10), 3) guarantee no repeats but [random.randint(0,9) for _ in range(3)] does not?
sample draws without replacement (picks distinct items), while independent randint calls can each land on the same value, so duplicates are possible.Why is os split from sys when both feel like "system stuff"?
os talks to the operating system (files, directories, environment variables); sys talks to the Python interpreter itself (command-line args, exit codes, import paths). Different audiences.Why does lru_cache turn naive Fibonacci from into ?
Naive
fib recomputes the same subproblems exponentially; the cache stores each fib(k) once so every value is computed a single time — see Recursion and Memoization.Why prefer functools.wraps when writing a decorator?
Without it the wrapped function loses its real name and docstring (they become the wrapper's), which breaks debugging and help output.
wraps copies that metadata across.Edge cases
Counter("") on an empty string — what comes back and does it error?
No error: an empty
Counter(). Any missing key still safely returns 0 rather than raising, which is one of Counter's conveniences.math.comb(3, 5) — choosing more than you have.
Returns
0, not an error. There are genuinely zero ways to choose 5 items from 3, matching the combinatorial definition (see Combinatorics).math.factorial(0) — is zero a legal input?
Yes,
0! = 1 by the empty-product convention. Negative or non-integer inputs do raise, though.permutations('ABC', 0) — asking for length-0 arrangements.
Yields exactly one item: the empty tuple
(). There is precisely one way to arrange nothing, so the count holds — see Combinatorics.deque(maxlen=2) after three appends — where does the extra go?
A bounded deque silently drops from the opposite end to stay within
maxlen. Appending on the right with a full deque pushes the leftmost item off, which is handy for "last N items" buffers.random.choice([]) on an empty list — safe or not?
Not safe; it raises
IndexError because there is nothing to choose from. Guard the length before calling if the list might be empty.sys.exit(0) versus sys.exit(1) — do they behave differently to the shell?
Yes.
0 signals success, any nonzero signals an error to the calling shell/script. The number is the process's exit status, not just a value Python ignores.groupby on unsorted data like [1,1,2,1] — does it group all the 1s together?
No.
itertools.groupby only groups consecutive equal items, so you get three groups (1,1 / 2 / 1). Sort first if you want all equal keys merged — a classic trap (see Python Iterators and Generators).