PCAP Closures and Decorators 2 — Questions and Answers
Question 1: What is a decorator in Python?
- A callable that takes a function as an argument, extends its behavior, and returns a new callable (Correct answer)
- A special comment syntax that annotates function signatures for type checking
- A built-in design pattern that replaces multiple inheritance
- A Python keyword that formats string output
Correct answer: A callable that takes a function as an argument, extends its behavior, and returns a new callable
A decorator is a higher-order function that wraps another function to add behavior before, after, or around the original call without modifying its source code.
Question 2: What is the `@my_decorator` syntax exactly equivalent to when placed above `def func(): ...`?
- func = my_decorator(func) (Correct answer)
- func = func(my_decorator)
- my_decorator.apply(func)
- func.__decorator__ = my_decorator
Correct answer: func = my_decorator(func)
`@my_decorator` is syntactic sugar: Python calls `my_decorator(func)` and rebinds the name `func` to whatever is returned.
Question 3: What does `@functools.wraps(func)` do when applied inside a decorator's wrapper function?
- Copies the wrapped function's metadata (__name__, __doc__, __module__, etc.) onto the wrapper (Correct answer)
- Makes the wrapper function execute faster by bypassing Python's call overhead
- Prevents the decorator from being stacked with other decorators
- Creates a deep copy of the original function's bytecode
Correct answer: Copies the wrapped function's metadata (__name__, __doc__, __module__, etc.) onto the wrapper
`@functools.wraps(func)` preserves the original function's identity attributes on the wrapper so introspection tools and documentation generators see the correct name and docstring.
Question 4: When the following decorators are applied, in what order are they executed? @decorator_a @decorator_b def func(): pass
- decorator_b wraps func first (innermost), then decorator_a wraps the result (outermost) (Correct answer)
- decorator_a is applied first, then decorator_b is applied to that result
- Both decorators are applied simultaneously in an unspecified order
- The order is determined by each decorator's priority attribute
Correct answer: decorator_b wraps func first (innermost), then decorator_a wraps the result (outermost)
Decorators are applied bottom-up: `decorator_b(func)` is evaluated first, and then `decorator_a` receives that result.
Question 5: What will `print(greet.__name__)` output? import functools def my_decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper @my_decorator def greet(): pass
- greet (Correct answer)
- wrapper
- my_decorator
- None
Correct answer: greet
Because `@functools.wraps(func)` is applied, the wrapper inherits the original function's `__name__`, so it prints `greet`.
Question 6: How is a decorator that accepts its own arguments (a parameterized decorator) structured?
- An outer function accepts the arguments and returns a decorator function that accepts and wraps the target function (Correct answer)
- Arguments are passed directly in the @decorator(args) call and the function takes them as *args
- The decorator class uses __init__ to store arguments and __call__ to act as the wrapper directly
- Arguments are attached as attributes on the decorator function before applying it
Correct answer: An outer function accepts the arguments and returns a decorator function that accepts and wraps the target function
A parameterized decorator requires one extra nesting level: `@repeat(3)` means `repeat(3)` runs first and must return a regular (no-argument) decorator.
Question 7: Why should a decorator's wrapper function use `*args, **kwargs` instead of matching the wrapped function's exact signature?
- So the decorator is generic and can wrap any function regardless of its parameter signature (Correct answer)
- To convert all positional arguments into keyword arguments automatically
- To prevent the wrapped function from receiving unintended arguments
- To make the wrapper behave as a generator function
Correct answer: So the decorator is generic and can wrap any function regardless of its parameter signature
Using `*args, **kwargs` makes the wrapper transparent — it accepts and forwards any combination of arguments — so one decorator can work with functions of any signature.
What is a decorator in Python?