What are Decorators in Python? With syntax and example.
Decorators in Python are special functions that allow you to modify or enhance the behavior of other functions or methods without changing their code.
They "wrap" another function to add extra functionality.
Syntax:
@decorator_name
def function_to_decorate():
# Function logic
Example:
def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
# Calling the decorated function
say_hello()
Output:
Something is happening before the function is called.
Hello!
Something is happening after the function is called.
Decorators are a powerful feature in Python that allow you to enhance functions without changing their actual code.
Why do we need decorators?
- Code Reusability: Decorators help in reusing code, making it easier to apply the same behavior to multiple functions.
- Separation of Concerns: They allow you to separate concerns, such as logging, access control, or performance measurement, from the core logic of the function.
- Cleaner Code: Using decorators makes your code cleaner and more readable.
How to Handle Arguments and Parameters in Decorators?
When decorating functions that take arguments, we need to make our decorator flexible enough to accept any number of arguments and keyword arguments.
This is done by using *args and **kwargs in the decorator's inner function.
Syntax:
To create a decorator that handles arguments, we use *args and **kwargs in both the wrapper function inside the decorator and in the function call itself.
def decorator_name(func):
def wrapper(*args, **kwargs):
# Code to run before the function call
result = func(*args, **kwargs)
# Code to run after the function call
return result
return wrapper
Example:
Here’s an example of a decorator that can handle any number of arguments and keyword arguments:
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Arguments were:", args, kwargs)
result = func(*args, **kwargs)
print("Result of the function:", result)
return result
return wrapper
@my_decorator
def add(x, y):
return x + y
@my_decorator
def greet(name="World"):
return f"Hello, {name}!"
# Calling the decorated functions
add(3, 5)
greet(name="Alice")
Output:
Arguments were: (3, 5) {}
Result of the function: 8
Arguments were: () {'name': 'Alice'}
Result of the function: Hello, Alice!
By using *args and **kwargs, we can apply the same decorator to any function, regardless of its parameters. This is a powerful way to enhance functions without changing their underlying code.
20 important and unique Python decorator questions for interviews, complete with answers and examples:
Question 1. What is a decorator in Python, and how does it work?
Answer: A decorator in Python is a function that takes another function as input and extends or modifies its behavior without changing its actual code.
Example:
def decorator(func):
def wrapper():
print("Before the function")
func()
print("After the function")
return wrapper
@decorator
def say_hello():
print("Hello")
say_hello()
Output:
Before the function
Hello
After the function
Question 2. How can you create a decorator that accepts arguments?
Answer: To create a decorator that accepts arguments, you need to wrap the decorator in an outer function that takes the arguments.
Example:
def repeat(n):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(n):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def greet():
print("Hello")
greet()
Output:
Hello
Hello
Hello
Question 3. Explain the use of *args and **kwargs in decorators.
Answer: Using *args and **kwargs allows the decorator to handle any number of positional and keyword arguments, making it flexible for various functions.
Example:
def decorator(func):
def wrapper(*args, **kwargs):
print("Arguments:", args, kwargs)
return func(*args, **kwargs)
return wrapper
@decorator
def add(a, b):
return a + b
print(add(3, 4))
Output:
Arguments: (3, 4) {}
7
Question 4. What is a higher-order function, and why is it relevant to decorators?
Answer: A higher-order function is a function that takes another function as an argument or returns a function. Decorators are higher-order functions because they take a function and return a modified version.
Question 5. Can a decorator modify the return value of a function?
Answer: Yes, a decorator can modify or replace the return value of a function.
Example:
def modify_return(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result * 2
return wrapper
@modify_return
def get_value():
return 10
print(get_value())
Output:
20
Question 6. How do you create a decorator to add logging to a function?
Answer: You can create a logging decorator to print messages before and after the function execution.
Example:
def log(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result}")
return result
return wrapper
@log
def add(a, b):
return a + b
add(5, 3)
Output:
Calling add
add returned 8
Question 7. How can you decorate a class method?
Answer: Decorate it in the same way as a regular function, but the decorator must handle self as an argument.
Example:
def my_decorator(func):
def wrapper(self):
print("Decorator applied")
return func(self)
return wrapper
class MyClass:
@my_decorator
def greet(self):
print("Hello")
obj = MyClass()
obj.greet()
Output:
Decorator applied
Hello
Question 8. What is the @classmethod decorator, and how does it work?
Answer: The @classmethod decorator converts a method to a class method, which receives the class (cls) as its first argument instead of the instance.
Example:
class MyClass:
@classmethod
def hello(cls):
print(f"Hello from {cls}")
MyClass.hello()
Output:
Hello from <class '__main__.MyClass'>
Question 9. What’s the best way to debug a decorated function?
Answer: Using functools.wraps, logging, or directly testing the decorator separately are effective methods.
Question 10. What is the @staticmethod decorator, and when should you use it?
Answer: @staticmethod defines a method that doesn’t receive self or cls and can be called directly on the class without an instance.
Example:
class MyClass:
@staticmethod
def hello():
print("Hello, World")
MyClass.hello()
Output:
Hello, World
Question 11. How can you stack multiple decorators on a single function?
Answer: You can apply multiple decorators by stacking them on top of each other.
Example:
def decorator1(func):
def wrapper(*args, **kwargs):
print("Decorator 1")
return func(*args, **kwargs)
return wrapper
def decorator2(func):
def wrapper(*args, **kwargs):
print("Decorator 2")
return func(*args, **kwargs)
return wrapper
@decorator1
@decorator2
def say_hello():
print("Hello!")
say_hello()
Output:
Decorator 1
Decorator 2
Hello!
Question 12. How would you create a decorator that times the execution of a function?
Example:
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"Execution time: {end - start}s")
return result
return wrapper
@timer
def example():
time.sleep(1)
print("Done!")
example()
Output:
Done!
Execution time: 1.0001440048217773s
Question 13. What is functools.wraps and why is it important in decorators?
Answer: functools.wraps preserves the metadata (like function name, docstring) of the original function when creating decorators.
Example:
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
Question 14. How can you use a decorator to handle exceptions in a function?
Example:
def catch_exceptions(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
print("Error:", e)
return wrapper
@catch_exceptions
def divide(x, y):
return x / y
divide(5, 0)
Output:
ERROR!
Error: division by zero
Question 15. Can decorators be used with lambda functions?
Answer: Yes, but decorators are typically used with named functions. Lambda functions would need to be assigned to a variable for this to work.
Question 16. How would you create a decorator that retries a function on failure?
Example:
def retry(func):
def wrapper(*args, **kwargs):
for _ in range(3):
try:
return func(*args, **kwargs)
except Exception as e:
print("Retrying...")
return wrapper
@retry
def unstable_function():
raise ValueError("Failure!")
unstable_function()
Output:
Retrying...
Retrying...
Retrying...
Question 17. How do decorators work with asynchronous functions?
Answer: You need to make the wrapper function asynchronous as well using async def.
Question 18. What is the difference between @property and a regular decorator?
Answer: @property turns a method into a read-only attribute, accessed like a variable instead of a function.
Question 19. How can you apply decorators to classes instead of functions?
Answer: You can apply a decorator directly to a class, affecting the whole class rather than a single function.
Question 20. Can a decorator return multiple functions?
Answer: No, a decorator typically returns a single modified function. However, you can return a wrapper that chooses which function to call.
Tags:
Leave a comment
You must be logged in to post a comment.