Advanced Functions Quiz
Quiz
Question 1 of 35
(0 answered)
Question 1
What is the primary purpose of a decorator in Python?
✓
Correct!
Decorators wrap other functions to extend or modify their behavior without changing the original function’s code — like a protective phone case that adds features without altering the phone itself.
✗
Incorrect
Decorators wrap other functions to extend or modify their behavior without changing the original function’s code — like a protective phone case that adds features without altering the phone itself.
Think about the phone case analogy: same phone, extra features.
Question 2
Given
@decorator1 @decorator2 def f(): pass, this is equivalent to f = decorator1(decorator2(f)).✓
Correct!
Decorators are applied bottom-up.
@decorator2 is applied first (innermost), then @decorator1 wraps the result — equivalent to f = decorator1(decorator2(f)).✗
Incorrect
Decorators are applied bottom-up.
@decorator2 is applied first (innermost), then @decorator1 wraps the result — equivalent to f = decorator1(decorator2(f)).Read the decorator stack from bottom to top to determine the order of application.
Question 3
Predict the output:
class CountCalls:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"Call {self.count} of {self.func.__name__}")
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
print("Hello!")
say_hello()
say_hello()What will this code output?
✓
Correct!
Each call to
say_hello() triggers __call__, which increments count, prints the call number, then calls the original function which prints ‘Hello!’. Two calls produce four lines of output total.✗
Incorrect
Each call to
say_hello() triggers __call__, which increments count, prints the call number, then calls the original function which prints ‘Hello!’. Two calls produce four lines of output total.The
__call__ method runs first, then delegates to the original function.Question 4
What does
@wraps(func) from functools preserve when used inside a decorator?✓
Correct!
@wraps(func) copies metadata from the original function to the wrapper, preserving attributes like __name__, __doc__, and __qualname__. Without it, the decorated function would appear as the inner wrapper function when inspected.✗
Incorrect
@wraps(func) copies metadata from the original function to the wrapper, preserving attributes like __name__, __doc__, and __qualname__. Without it, the decorated function would appear as the inner wrapper function when inspected.What information about a function would be lost if you inspected it after decorating?
Question 5
What method do you call on an
lru_cache-decorated function to remove all its cached results? (e.g., fibonacci._____())✓
Correct!
fibonacci.cache_clear() removes all cached results. The companion method fibonacci.cache_info() returns statistics like hits, misses, and current cache size.✗
Incorrect
fibonacci.cache_clear() removes all cached results. The companion method fibonacci.cache_info() returns statistics like hits, misses, and current cache size.The method name describes its action using two words joined by an underscore.
Question 6
Predict the output:
from functools import partial
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
cube = partial(power, exponent=3)
print(square(5))
print(cube(3))What will this code output?
✓
Correct!
partial creates specialized functions with pre-filled arguments. square(5) calls power(5, exponent=2) = 5² = 25, and cube(3) calls power(3, exponent=3) = 3³ = 27.✗
Incorrect
partial creates specialized functions with pre-filled arguments. square(5) calls power(5, exponent=2) = 5² = 25, and cube(3) calls power(3, exponent=3) = 3³ = 27.Each specialized function fixes one argument — what does 5 squared and 3 cubed equal?
Question 7
Predict the output:
from functools import reduce
numbers = [1, 2, 3, 4, 5]
total = reduce(lambda x, y: x + y, numbers)
print(total)What will this code output?
✓
Correct!
reduce applies the function cumulatively from left to right: ((((1+2)+3)+4)+5) = 15. It folds the entire list into a single value using repeated application of the binary function.✗
Incorrect
reduce applies the function cumulatively from left to right: ((((1+2)+3)+4)+5) = 15. It folds the entire list into a single value using repeated application of the binary function.Think of reduce as collapsing step by step: 1+2=3, 3+3=6, 6+4=10, 10+5=?
Question 8
Predict the output:
import itertools
result = list(itertools.dropwhile(lambda x: x < 5, [1, 3, 6, 2, 1]))
print(result)What will this code output?
✓
Correct!
dropwhile drops elements while the predicate is True, then yields ALL remaining elements — even if some would have matched the predicate again. It drops 1 and 3 (both < 5), then yields 6, 2, 1 without re-checking the predicate for 2 and 1.✗
Incorrect
dropwhile drops elements while the predicate is True, then yields ALL remaining elements — even if some would have matched the predicate again. It drops 1 and 3 (both < 5), then yields 6, 2, 1 without re-checking the predicate for 2 and 1.Once dropwhile sees the first element that fails the predicate, it stops checking and passes everything through.
Question 9
itertools.groupby groups ALL elements with the same key together, regardless of their position in the iterable.✓
Correct!
groupby only groups CONSECUTIVE elements with the same key. If identical keys appear non-consecutively, they form separate groups. To group all matching elements regardless of position, sort the iterable by key first.✗
Incorrect
groupby only groups CONSECUTIVE elements with the same key. If identical keys appear non-consecutively, they form separate groups. To group all matching elements regardless of position, sort the iterable by key first.Consider what would happen with the input
['a', 'b', 'a'] — how many groups would form?Question 10
Which
itertools functions produce infinite sequences?✓
Correct!
count() counts indefinitely from a start value, cycle() repeats an iterable forever, and repeat() repeats an element infinitely when no times argument is given. chain() and combinations() are finite — they terminate when their input is exhausted.✗
Incorrect
count() counts indefinitely from a start value, cycle() repeats an iterable forever, and repeat() repeats an element infinitely when no times argument is given. chain() and combinations() are finite — they terminate when their input is exhausted.Which ones would loop forever without a break condition or an explicit stop argument?
Question 11
Predict the output:
import itertools
result = list(itertools.chain([1, 2], [3, 4], [5]))
print(result)What will this code output?
✓
Correct!
itertools.chain concatenates multiple iterables into a single flat sequence, yielding elements from the first iterable, then the second, and so on — without creating nested lists.✗
Incorrect
itertools.chain concatenates multiple iterables into a single flat sequence, yielding elements from the first iterable, then the second, and so on — without creating nested lists.chain ‘chains’ iterables end-to-end, flattening one level.
Question 12
A class-based decorator must implement the
__call__ method to make its instances callable like a function.✓
Correct!
When a class is used as a decorator (e.g.,
@CountCalls), the class instance replaces the function. For the instance to be called like a function, it must implement __call__. Without it, calling the decorated function would raise a TypeError.✗
Incorrect
When a class is used as a decorator (e.g.,
@CountCalls), the class instance replaces the function. For the instance to be called like a function, it must implement __call__. Without it, calling the decorated function would raise a TypeError.What special method makes a Python object callable?
Question 13
What is a decorator factory and when would you use one?
What is a decorator factory and when would you use one?
A decorator factory is a function that returns a decorator.
Use one when your decorator needs to accept arguments:
@repeat(times=3)— the factory takestimes, returns a decorator@retry(max_attempts=3, delay=2)— factory takes retry config
Structure:
outer(args) → decorator(func) → wrapper(*args, **kwargs)Three levels of nesting: factory → decorator → wrapper.
Did you get it right?
✓
Correct!
✗
Incorrect
Question 14
Arrange the lifecycle stages of a
@contextmanager-decorated generator in the correct execution order:Drag to arrange in the correct execution order
⋮⋮
yield — control passes to the
with block⋮⋮
Setup code before
yield (equivalent to __enter__)⋮⋮
Teardown code after
yield in finally (equivalent to __exit__)✓
Correct!
A
@contextmanager generator runs setup code first (acquiring resources), then yield hands control to the with block body. After the with block finishes (or raises an exception), execution resumes after yield — typically in a finally block for guaranteed cleanup.✗
Incorrect
A
@contextmanager generator runs setup code first (acquiring resources), then yield hands control to the with block body. After the with block finishes (or raises an exception), execution resumes after yield — typically in a finally block for guaranteed cleanup.Question 15
Complete the
@contextmanager timer so it correctly passes control to the with block:Fill in the missing keyword
from contextlib import contextmanager
import time
@contextmanager
def timer(label):
start = time.time()
try:
_____
finally:
end = time.time()
print(f"{label}: {end - start:.4f}s")
with timer("query"):
pass✓
Correct!
yield is the keyword that splits setup from teardown in a @contextmanager generator. Code before yield runs on entry; code after yield (in finally) runs on exit. Without yield, the function is not a valid context manager.✗
Incorrect
yield is the keyword that splits setup from teardown in a @contextmanager generator. Code before yield runs on entry; code after yield (in finally) runs on exit. Without yield, the function is not a valid context manager.What keyword suspends a generator and passes control back to the caller?
Question 16
What does
contextlib.suppress(FileNotFoundError) do when used as a context manager?✓
Correct!
contextlib.suppress is a clean alternative to try/except: pass. It silently swallows the specified exception types, allowing execution to continue. Useful when an operation might fail and you explicitly want to ignore that failure.✗
Incorrect
contextlib.suppress is a clean alternative to try/except: pass. It silently swallows the specified exception types, allowing execution to continue. Useful when an operation might fail and you explicitly want to ignore that failure.The name ‘suppress’ describes exactly what it does to the exception.
Question 17
What does
@singledispatch from functools enable?✓
Correct!
@singledispatch enables type-based function overloading. You define a base implementation and register type-specific variants with @func.register(type). Python selects the correct variant based on the runtime type of the first argument.✗
Incorrect
@singledispatch enables type-based function overloading. You define a base implementation and register type-specific variants with @func.register(type). Python selects the correct variant based on the runtime type of the first argument.Think of it as Python’s way to achieve method overloading found in statically-typed languages.
Question 18
Which of the following are provided by the
functools module?✓
Correct!
wraps, lru_cache, partial, singledispatch, and reduce are all from functools. chain and compress are from itertools. A useful heuristic: functools is about higher-order functions and function manipulation; itertools is about efficient iteration.✗
Incorrect
wraps, lru_cache, partial, singledispatch, and reduce are all from functools. chain and compress are from itertools. A useful heuristic: functools is about higher-order functions and function manipulation; itertools is about efficient iteration.functools = function tools; itertools = iterator tools. Which module does each name logically belong to?
Question 19
Predict the output:
import itertools
result = list(itertools.combinations([1, 2, 3], 2))
print(result)What will this code output?
✓
Correct!
combinations([1, 2, 3], 2) generates all 2-element subsets where order does not matter. It produces 3 pairs: (1,2), (1,3), (2,3). permutations would produce 6 tuples since (1,2) and (2,1) are treated as distinct arrangements.✗
Incorrect
combinations([1, 2, 3], 2) generates all 2-element subsets where order does not matter. It produces 3 pairs: (1,2), (1,3), (2,3). permutations would produce 6 tuples since (1,2) and (2,1) are treated as distinct arrangements.In combinations, order doesn’t matter — (1,2) and (2,1) count as the same pair.
Question 20
What is the key difference between
itertools.combinations and itertools.permutations?What is the key difference between
itertools.combinations and itertools.permutations?Combinations — order does NOT matter:
(1, 2)and(2, 1)are the samecombinations([1,2,3], 2)→ 3 results:(1,2), (1,3), (2,3)- Formula: C(n,r) = n! / (r!(n-r)!)
Permutations — order DOES matter:
(1, 2)and(2, 1)are differentpermutations([1,2,3], 2)→ 6 results:(1,2), (1,3), (2,1), (2,3), (3,1), (3,2)- Formula: P(n,r) = n! / (n-r)!
Memory trick: Combinations = Choose (order irrelevant); Permutations = Position (order matters).
Did you get it right?
✓
Correct!
✗
Incorrect
Question 21
Predict the output:
import itertools
result = list(itertools.takewhile(lambda x: x < 5, [1, 3, 6, 2, 1]))
print(result)What will this code output?
✓
Correct!
takewhile yields elements while the predicate is True, then STOPS immediately — even if later elements would have passed. It takes 1 and 3 (both < 5), then stops at 6. Unlike filter, it does not continue checking the rest of the sequence.✗
Incorrect
takewhile yields elements while the predicate is True, then STOPS immediately — even if later elements would have passed. It takes 1 and 3 (both < 5), then stops at 6. Unlike filter, it does not continue checking the rest of the sequence.Compare with
dropwhile: one stops taking, the other stops dropping — both at the first failure.Question 22
Predict the output:
import itertools
result = list(itertools.islice(range(10), 2, 8, 2))
print(result)What will this code output?
✓
Correct!
islice(iterable, start, stop, step) works like Python’s slice notation. Start=2, stop=8 (exclusive), step=2 → elements at positions 2, 4, 6: values 2, 4, 6. Position 8 is excluded, so 8 is not included.✗
Incorrect
islice(iterable, start, stop, step) works like Python’s slice notation. Start=2, stop=8 (exclusive), step=2 → elements at positions 2, 4, 6: values 2, 4, 6. Position 8 is excluded, so 8 is not included.Think of it as
range(10)[2:8:2] but for any iterator.Question 23
Predict the output:
import itertools
result = list(itertools.zip_longest([1, 2], ['a', 'b', 'c'], fillvalue='?'))
print(result)What will this code output?
✓
Correct!
zip_longest pads shorter iterables with fillvalue instead of truncating. The built-in zip would stop at the shortest iterable (2 pairs); zip_longest continues to the longest (3 pairs), using '?' for the missing first element.✗
Incorrect
zip_longest pads shorter iterables with fillvalue instead of truncating. The built-in zip would stop at the shortest iterable (2 pairs); zip_longest continues to the longest (3 pairs), using '?' for the missing first element.What happens to the shorter iterable once it runs out of elements?
Question 24
Predict the output:
import itertools
result = list(itertools.product([1, 2], ['a', 'b']))
print(result)What will this code output?
✓
Correct!
itertools.product computes the Cartesian product — every combination of one element from each iterable. It’s equivalent to nested for loops: for x in [1,2]: for y in ['a','b']. 2×2 = 4 tuples total.✗
Incorrect
itertools.product computes the Cartesian product — every combination of one element from each iterable. It’s equivalent to nested for loops: for x in [1,2]: for y in ['a','b']. 2×2 = 4 tuples total.Think of a multiplication table — every row value paired with every column value.
Question 25
Predict the output:
import itertools
result = list(itertools.compress('ABCDEF', [1, 0, 1, 0, 1, 1]))
print(result)What will this code output?
✓
Correct!
compress applies a boolean mask: it keeps elements from the data where the corresponding selector is truthy (1) and drops those where it is falsy (0). Positions 0,2,4,5 are 1 → A, C, E, F are kept.✗
Incorrect
compress applies a boolean mask: it keeps elements from the data where the corresponding selector is truthy (1) and drops those where it is falsy (0). Positions 0,2,4,5 are 1 → A, C, E, F are kept.Map each letter to its selector: A→1 (keep), B→0 (drop), C→1 (keep), D→0 (drop), E→1 (keep), F→1 (keep).
Question 26
Complete the code to flatten a nested list using
itertools:Fill in the missing method name
import itertools
nested = [[1, 2], [3, 4], [5]]
flat = list(itertools.chain._____(nested))
print(flat) # [1, 2, 3, 4, 5]✓
Correct!
chain.from_iterable(nested) takes a single iterable of iterables and chains them together — equivalent to chain(*nested) but more memory-efficient since it doesn’t unpack everything at once. Ideal for flattening one level of nesting.✗
Incorrect
chain.from_iterable(nested) takes a single iterable of iterables and chains them together — equivalent to chain(*nested) but more memory-efficient since it doesn’t unpack everything at once. Ideal for flattening one level of nesting.The method name describes where the chain’s elements come from.
Question 27
Predict the output:
from functools import reduce
numbers = [1, 2, 3, 4, 5]
product = reduce(lambda x, y: x * y, numbers, 1)
print(product)What will this code output?
✓
Correct!
The third argument to
reduce is an initial value. It starts with 1, then applies multiplication cumulatively: 1×1=1, 1×2=2, 2×3=6, 6×4=24, 24×5=120. The initial value is useful when the iterable might be empty (prevents TypeError).✗
Incorrect
The third argument to
reduce is an initial value. It starts with 1, then applies multiplication cumulatively: 1×1=1, 1×2=2, 2×3=6, 6×4=24, 24×5=120. The initial value is useful when the iterable might be empty (prevents TypeError).5! = 5 × 4 × 3 × 2 × 1
Question 28
What does
fibonacci.cache_info() return after calling an @lru_cache-decorated function several times?✓
Correct!
cache_info() returns a CacheInfo named tuple with four fields: hits (cache was used), misses (function actually ran), maxsize (configured limit), and currsize (current entries cached). Useful for tuning cache size.✗
Incorrect
cache_info() returns a CacheInfo named tuple with four fields: hits (cache was used), misses (function actually ran), maxsize (configured limit), and currsize (current entries cached). Useful for tuning cache size.Think about what statistics would help you evaluate whether the cache is effective.
Question 29
@lru_cache can cache results for functions that accept mutable arguments like lists or dictionaries.✓
Correct!
lru_cache caches results by hashing the arguments to create a cache key. Mutable types like lists and dicts are not hashable in Python, so passing them raises a TypeError. All arguments must be hashable (e.g., ints, strings, tuples).✗
Incorrect
lru_cache caches results by hashing the arguments to create a cache key. Mutable types like lists and dicts are not hashable in Python, so passing them raises a TypeError. All arguments must be hashable (e.g., ints, strings, tuples).What does a cache key need to be — and what makes a type hashable?
Question 30
In the @retry decorator below, when does it re-raise the exception instead of retrying?
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts:
raise
time.sleep(delay)✓
Correct!
The decorator re-raises only when
attempt == max_attempts — meaning all retries are exhausted. Before that, it sleeps and loops again. This ensures the caller always sees the exception if the function never succeeds, rather than silently swallowing it.✗
Incorrect
The decorator re-raises only when
attempt == max_attempts — meaning all retries are exhausted. Before that, it sleeps and loops again. This ensures the caller always sees the exception if the function never succeeds, rather than silently swallowing it.The decorator must eventually give up — what condition signals that all attempts are spent?
Question 31
In the database transaction context manager below, what happens when an exception is raised inside the with block?
@contextmanager
def transaction(connection):
try:
yield connection
connection.commit()
except Exception:
connection.rollback()
raise✓
Correct!
When an exception propagates out of the
with block, execution jumps to the except clause — skipping commit(). rollback() undoes any partial changes, and raise re-raises the exception so the caller knows something went wrong. This is the standard safe transaction pattern.✗
Incorrect
When an exception propagates out of the
with block, execution jumps to the except clause — skipping commit(). rollback() undoes any partial changes, and raise re-raises the exception so the caller knows something went wrong. This is the standard safe transaction pattern.Trace the execution path when an exception occurs: does
commit() run before or after the exception?Question 32
What does
contextlib.redirect_stdout(f) do when used as a context manager?✓
Correct!
redirect_stdout temporarily redirects sys.stdout to the given file-like object for the duration of the with block. Any print() calls inside write to f instead of the console. After the block, stdout is restored to its original target.✗
Incorrect
redirect_stdout temporarily redirects sys.stdout to the given file-like object for the duration of the with block. Any print() calls inside write to f instead of the console. After the block, stdout is restored to its original target.The effect is temporary and scoped to the
with block — what happens to stdout inside vs outside?Question 33
Which of the following are appropriate use cases for context managers?
✓
Correct!
Context managers excel at resource management, temporary state changes, and guaranteed setup/teardown — all patterns where you need something to happen before AND after a block of code, even if an exception occurs. Caching belongs to
functools.lru_cache; type-based dispatch belongs to singledispatch.✗
Incorrect
Context managers excel at resource management, temporary state changes, and guaranteed setup/teardown — all patterns where you need something to happen before AND after a block of code, even if an exception occurs. Caching belongs to
functools.lru_cache; type-based dispatch belongs to singledispatch.Context managers are about the
with pattern: enter, do something, exit — reliably.Question 34
Predict the output:
import itertools
result = list(itertools.combinations_with_replacement([1, 2], 2))
print(result)What will this code output?
✓
Correct!
combinations_with_replacement allows an element to appear more than once in a combination. With [1, 2] choosing 2: (1,1), (1,2), (2,2). Compare to combinations([1,2], 2) which only gives (1,2) — no repeats allowed.✗
Incorrect
combinations_with_replacement allows an element to appear more than once in a combination. With [1, 2] choosing 2: (1,1), (1,2), (2,2). Compare to combinations([1,2], 2) which only gives (1,2) — no repeats allowed.The ‘with_replacement’ part means the same element can be chosen more than once.
Question 35
Predict the output:
import itertools
result = list(itertools.permutations([1, 2, 3], 2))
print(len(result))What will this code output?
✓
Correct!
permutations([1,2,3], 2) produces all ordered 2-element arrangements: (1,2),(1,3),(2,1),(2,3),(3,1),(3,2) — 6 total. Unlike combinations, order matters: (1,2) and (2,1) are distinct. Formula: P(3,2) = 3!/(3-2)! = 6.✗
Incorrect
permutations([1,2,3], 2) produces all ordered 2-element arrangements: (1,2),(1,3),(2,1),(2,3),(3,1),(3,2) — 6 total. Unlike combinations, order matters: (1,2) and (2,1) are distinct. Formula: P(3,2) = 3!/(3-2)! = 6.With 3 choices for the first position and 2 remaining for the second, how many pairs are there?
Quiz Results
Score
0/0
Accuracy
0%
Right
0
Wrong
Skipped
0
Last updated on