Quiz#

This quiz covers the key concepts from Lecture 5: Advanced Functions, including programming paradigms, first-class functions, lambda expressions, closures, callables, decorators, functools.wraps, stacking decorators, decorators with arguments, and functools.partial.

Note

Instructions:

  • Answer all questions to the best of your ability.

  • Multiple choice questions have exactly one correct answer.

  • True/False questions require you to determine if the statement is correct.

  • Essay questions require short written responses (2-4 sentences).

  • Click the dropdown after each question to reveal the answer.


Multiple Choice#

Question 1

What is the output of the following code?

def make_multiplier(n):
    return lambda x: x * n

double = make_multiplier(2)
print(double(5))
  1. 5

  2. 10

  3. 2

  4. TypeError

Answer

B10

make_multiplier(2) returns a lambda that multiplies its argument by 2. Calling double(5) evaluates 5 * 2 = 10.

Question 2

Which of the following is NOT a valid use of a lambda?

  1. sorted(items, key=lambda x: x[1])

  2. lambda x, y: x + y

  3. lambda x: if x > 0: return x

  4. list(map(lambda x: x**2, [1, 2, 3]))

Answer

Clambda x: if x > 0: return x

Lambdas can only contain a single expression. if/return statements are not allowed. Conditional expressions (x if x > 0 else 0) are allowed, but statement-based if blocks are not.

Question 3

What is the output of the following code?

def outer():
    count = 0
    def inner():
        nonlocal count
        count += 1
        return count
    return inner

f = outer()
print(f(), f(), f())
  1. 1 1 1

  2. 1 2 3

  3. 0 1 2

  4. NameError

Answer

B1 2 3

outer() creates a closure. Each call to f() increments the captured count variable via nonlocal. The three calls produce 1, 2, and 3.

Question 4

What does the @ syntax do in the following code?

@my_decorator
def my_function():
    pass
  1. It calls my_function and passes the result to my_decorator.

  2. It is equivalent to my_function = my_decorator(my_function).

  3. It creates a new class called my_decorator.

  4. It passes my_decorator as an argument to my_function.

Answer

B – It is equivalent to my_function = my_decorator(my_function).

The @decorator syntax is syntactic sugar. Python calls the decorator with the function as its argument and replaces the function with the return value.

Question 5

What is the purpose of functools.wraps?

  1. It makes a function run faster.

  2. It copies the original function’s metadata (name, docstring) onto the wrapper.

  3. It prevents a function from being decorated.

  4. It automatically adds type hints to the wrapper function.

Answer

B – It copies the original function’s metadata (name, docstring) onto the wrapper.

Without @wraps(func), the wrapper replaces the original function’s __name__, __doc__, and other attributes. functools.wraps preserves these for introspection and debugging.

Question 6

What is the output of the following code?

from functools import partial

def power(base, exponent):
    return base ** exponent

square = partial(power, exponent=2)
print(square(5))
  1. 10

  2. 25

  3. 32

  4. TypeError

Answer

B25

partial(power, exponent=2) creates a new function with exponent fixed to 2. Calling square(5) computes 5 ** 2 = 25.

Question 7

When multiple decorators are stacked, in what order are they applied?

@decorator_a
@decorator_b
def func():
    pass
  1. decorator_a is applied first, then decorator_b.

  2. decorator_b is applied first, then decorator_a.

  3. Both are applied simultaneously.

  4. The order depends on the function’s arguments.

Answer

Bdecorator_b is applied first, then decorator_a.

Stacked decorators are applied bottom to top. This is equivalent to func = decorator_a(decorator_b(func)). The innermost decorator (closest to the function) is applied first.

Question 8

What does callable(42) return?

  1. True

  2. False

  3. 42

  4. TypeError

Answer

BFalse

Integers are not callable. Only objects that can be invoked with parentheses (functions, classes, objects with __call__) return True from callable().

Question 9

What is the output of the following code?

def make_greeter(greeting):
    def greet(name):
        return f"{greeting}, {name}!"
    return greet

hi = make_greeter("Hi")
hello = make_greeter("Hello")
print(hi("Bob"))
  1. "Hello, Bob!"

  2. "Hi, Bob!"

  3. "greeting, Bob!"

  4. NameError

Answer

B"Hi, Bob!"

Each call to make_greeter creates an independent closure. hi captures "Hi" and hello captures "Hello". They do not interfere with each other.

Question 10

Which of the following correctly describes a higher-order function?

  1. A function that uses recursion.

  2. A function that takes another function as an argument or returns a function.

  3. A function defined inside a class.

  4. A function with more than three parameters.

Answer

B – A function that takes another function as an argument or returns a function.

Higher-order functions operate on other functions. Examples include map, filter, sorted (with key), and any decorator.

Question 11

What is the output of the following code?

nums = [1, 2, 3, 4, 5]
result = list(filter(lambda x: x % 2 == 0, nums))
print(result)
  1. [1, 3, 5]

  2. [2, 4]

  3. [1, 2, 3, 4, 5]

  4. []

Answer

B[2, 4]

filter keeps elements for which the lambda returns True. The lambda checks if a number is even (x % 2 == 0), so only 2 and 4 are kept.

Question 12

What is the output of the following code?

def repeat(n):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(n):
                func(*args, **kwargs)
        return wrapper
    return decorator

@repeat(2)
def say_hi():
    print("Hi")

say_hi()
  1. Hi (printed once)

  2. Hi (printed twice)

  3. TypeError

  4. Nothing is printed.

Answer

BHi (printed twice)

@repeat(2) creates a parameterized decorator. repeat(2) returns decorator, which wraps say_hi so that calling it executes the original function 2 times.

Question 13

Which of the following is a requirement for a closure?

  1. The inner function must be defined with lambda.

  2. The inner function must reference a variable from the enclosing function’s scope.

  3. The enclosing function must use the global keyword.

  4. The inner function must accept *args and **kwargs.

Answer

B – The inner function must reference a variable from the enclosing function’s scope.

A closure requires: a nested function, a reference to a free variable from the enclosing scope, and the enclosing function returning the nested function.

Question 14

What is the output of the following code?

def compute_square(x):
    return x ** 2

f = compute_square
print(f(4))
print(type(f))
  1. 16 then <class 'int'>

  2. 16 then <class 'function'>

  3. TypeError

  4. None then <class 'function'>

Answer

B16 then <class 'function'>

f = compute_square assigns the function object (not the return value) to f. Calling f(4) returns 16, and type(f) is <class 'function'>.

Question 15

What is the key difference between map and filter?

  1. map transforms each element; filter selects elements based on a condition.

  2. map returns a list; filter returns a tuple.

  3. map works with strings only; filter works with numbers only.

  4. map modifies the original list; filter creates a copy.

Answer

Amap transforms each element; filter selects elements based on a condition.

map(func, iterable) applies func to every element and returns the transformed values. filter(func, iterable) returns only those elements for which func returns True. Both return lazy iterators.


True or False#

Question 16

True or False: In Python, functions are first-class objects and can be assigned to variables, passed as arguments, and returned from other functions.

Answer

True

Functions in Python are first-class objects. They can be assigned to variables, passed to other functions as arguments, returned from functions, and stored in data structures like lists and dictionaries.

Question 17

True or False: A lambda function can contain multiple statements separated by semicolons.

Answer

False

Lambda functions are restricted to a single expression. They cannot contain statements (assignments, loops, try/except, etc.). For multi-line logic, use a regular def function.

Question 18

True or False: A closure’s captured variables are destroyed when the enclosing function returns.

Answer

False

The captured variables survive through cell objects stored in the inner function’s __closure__ tuple. Even after the enclosing function returns and its local scope is discarded, the cell objects keep the captured values alive.

Question 19

True or False: functools.partial calls the original function immediately with the frozen arguments.

Answer

False

functools.partial does not call the function. It returns a new callable with some arguments pre-filled. The function is only called when you invoke the partial object with the remaining arguments.

Question 20

True or False: The nonlocal keyword is required to read a variable from an enclosing scope inside a nested function.

Answer

False

You can read variables from an enclosing scope without nonlocal. The nonlocal keyword is only required when you want to modify (reassign) a variable in the enclosing scope.

Question 21

True or False: Decorators can only be applied to functions, not to classes or methods.

Answer

False

Decorators can be applied to functions, methods, and classes. For example, @staticmethod, @classmethod, and @dataclass are all commonly used decorators applied to methods or classes.

Question 22

True or False: map and filter return lists in Python 3.

Answer

False

In Python 3, map and filter return lazy iterators, not lists. You must wrap the result in list() to materialize all values.

Question 23

True or False: PEP 8 discourages assigning a lambda to a variable name.

Answer

True

PEP 8 states that assigning a lambda to a variable (e.g., f = lambda x: x + 1) defeats the purpose of lambdas. If you need a named function, use def instead. Lambdas are intended for short, inline use.

Question 24

True or False: Two closures created by the same enclosing function share the same captured state.

Answer

False

Each call to the enclosing function creates a new, independent closure with its own set of captured variables. Two closures from the same factory do not share state.

Question 25

True or False: A parameterized decorator (decorator with arguments) requires three levels of nested functions.

Answer

True

A parameterized decorator uses three layers: the decorator factory (takes the arguments), the decorator (takes the function), and the wrapper (replaces the function). The pattern is factory(args) -> decorator(func) -> wrapper(*args, **kwargs).


Essay Questions#

Question 26

Explain what a closure is and the three conditions required for one to exist. Provide a brief example.

(2-4 sentences)

Answer Guidelines

Key points to include:

  • A closure is a function that retains access to variables from its enclosing scope even after the enclosing function has returned.

  • Three conditions: (1) a nested function exists, (2) the nested function references a free variable from the enclosing scope, and (3) the enclosing function returns the nested function.

  • Python uses cell objects to keep the captured variables alive after the enclosing scope is discarded.

  • Example: a make_counter function that returns an increment function which remembers and updates a count variable.

Question 27

Explain why ``functools.wraps`` is important when writing decorators. What happens if you omit it?

(2-4 sentences)

Answer Guidelines

Key points to include:

  • When a decorator wraps a function, the wrapper function replaces the original. Without @wraps, the original function’s __name__, __doc__, __module__, and other metadata are lost.

  • functools.wraps copies these attributes from the original function onto the wrapper.

  • This is important for debugging (stack traces show the correct name), documentation generators, and any tool that inspects function metadata.

  • Best practice: always use @functools.wraps(func) in every decorator wrapper.

Question 28

Compare ``functools.partial``, lambda expressions, and closures as ways to pre-fill function arguments. When would you prefer each approach?

(2-4 sentences)

Answer Guidelines

Key points to include:

  • functools.partial is best for simple argument freezing; it preserves introspection via .func, .args, and .keywords attributes.

  • Lambda expressions are good for short, inline transformations where the logic fits in a single expression.

  • Closures offer the most flexibility: they can contain complex logic, maintain mutable state, and perform additional processing beyond simple argument binding.

  • Rule of thumb: use partial for straightforward cases, lambda for one-liners, and closures when you need state or multi-step logic.

Question 29

Explain how stacked decorators are applied. Given @A on top of @B on top of a function f, describe the order of execution.

(2-4 sentences)

Answer Guidelines

Key points to include:

  • Stacked decorators are applied bottom to top: @B is applied first, then @A.

  • The equivalent expression is f = A(B(f)).

  • At call time, the outermost wrapper (from A) executes first, then the wrapper from B, then the original function f.

  • This means the decorator closest to the function definition is applied first during decoration, but its wrapper is called last during execution.

Question 30

Describe the difference between a pure function and an impure function. Why do functional programming advocates prefer pure functions?

(2-4 sentences)

Answer Guidelines

Key points to include:

  • A pure function depends only on its inputs and produces no side effects (no modifying external state, no I/O). Given the same inputs, it always returns the same output.

  • An impure function may modify global variables, mutate arguments, perform I/O, or depend on external state, making its behavior harder to predict.

  • Pure functions are preferred because they are easier to test, debug, and reason about. They also enable safe parallelism since they do not share mutable state.

  • In practice, most programs need some impure functions (for I/O, logging, etc.), but minimizing side effects improves code quality.