Hackerrank Decorators and Closures — Questions and Answers
Question 1: What is a closure in Python?
- A function that retains access to variables from its enclosing scope even after that scope has finished executing (Correct answer)
- A function defined inside a class
- A function that cannot be called from outside its module
- A function with no return statement
Correct answer: A function that retains access to variables from its enclosing scope even after that scope has finished executing
A closure is an inner function that captures and remembers variables from its enclosing function's scope. The enclosing scope's variables persist as long as the closure exists, even after the outer function returns.
Question 2: What does the following code print? python def make_multiplier(n): def multiply(x): return x * n return multiply double = make_multiplier(2) print(double(5))
- 10 (Correct answer)
- 2
- 5
- Error: n is not defined
Correct answer: 10
make_multiplier(2) returns the inner function multiply with n=2 captured in its closure. Calling double(5) executes multiply(5) with the remembered n=2, returning 5*2=10.
Question 3: What is the effect of applying a decorator using @syntax? python @my_decorator def greet(): return 'hello'
- It is equivalent to greet = my_decorator(greet) (Correct answer)
- It calls my_decorator() and then calls greet()
- It makes greet a method of my_decorator
- It applies my_decorator only when greet raises an exception
Correct answer: It is equivalent to greet = my_decorator(greet)
The @decorator syntax is syntactic sugar. @my_decorator above a function definition is exactly equivalent to writing greet = my_decorator(greet) immediately after the def block.
Question 4: Why is functools.wraps used inside a decorator?
- To preserve the original function's __name__ and __doc__ on the wrapper function (Correct answer)
- To make the decorator work with class methods
- To prevent the decorator from being applied more than once
- To ensure the wrapper function runs in a separate thread
Correct answer: To preserve the original function's __name__ and __doc__ on the wrapper function
Without @functools.wraps(func), the wrapper function replaces the original's metadata, making debugging harder. @functools.wraps(func) copies __name__, __doc__, and other attributes from the original function to the wrapper.
Question 5: What does the nonlocal keyword do in the following code? python def counter(): count = 0 def increment(): nonlocal count count += 1 return count return increment
- Allows increment() to modify the count variable in counter()'s scope (Correct answer)
- Makes count a global variable
- Creates a new local variable count inside increment()
- Raises a SyntaxError because count is already defined
Correct answer: Allows increment() to modify the count variable in counter()'s scope
Without nonlocal, count += 1 inside increment() would create a new local variable, causing an UnboundLocalError. nonlocal count tells Python to use the count from the nearest enclosing (non-global) scope — counter()'s frame.
Question 6: What is the output of the following decorator example? python def shout(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) return result.upper() return wrapper @shout def greet(name): return f'hello {name}' print(greet('world'))
- HELLO WORLD (Correct answer)
- hello world
- Hello World
- hello WORLD
Correct answer: HELLO WORLD
The shout decorator wraps greet so that its return value is passed through .upper(). greet('world') returns 'hello world', and the wrapper returns 'hello world'.upper() = 'HELLO WORLD'.
What is a closure in Python?