Decorators in Python are a powerful way to modify or extend the behavior of functions or methods without changing their code. Decorators are often used for tasks like logging, authentication, and adding additional functionality to functions. They are denoted by the "@" symbol and are applied above the function they decorate.
defsay_hello():print("World")say_hello()
How do we change the output without changing the say_hello() function?
Use Decorators
# Define a decorator functiondefhello_decorator(func):defwrapper():print("Hello,")func()# Call the original functionreturn wrapper# Use the decorator to modify the behavior of say_hello@hello_decoratordefsay_hello():print("World")# Call the decorated functionsay_hello()
Output
Hello,
World
If you want to replace the new line character and the end of the print statement, use end=''
# Define a decorator functiondefhello_decorator(func):defwrapper():print("Hello, ", end='')func()# Call the original functionreturn wrapper# Use the decorator to modify the behavior of say_hello@hello_decoratordefsay_hello():print("World")# Call the decorated functionsay_hello()
Hello, World
Multiple functions inside the Decorator
def hello_decorator(func):
def first_wrapper():
print("First wrapper, doing something before the second wrapper.")
#func()
def second_wrapper():
print("Second wrapper, doing something before the actual function.")
#func()
def main_wrapper():
first_wrapper() # Call the first wrapper
second_wrapper() # Then call the second wrapper, which calls the actual function
func()
return main_wrapper
@hello_decorator
def say_hello():
print("World")
say_hello()
Args & Kwargs
*args: This is used to represent positional arguments. It collects all the positional arguments passed to the decorated function as a tuple.
**kwargs: This is used to represent keyword arguments. It collects all the keyword arguments (arguments passed with names) as a dictionary.
from functools import wrapsdefmy_decorator(func):@wraps(func)defwrapper(*args,**kwargs):print("Positional Arguments (*args):", args)print("Keyword Arguments (**kwargs):", kwargs) result =func(*args, **kwargs)return resultreturn wrapper@my_decoratordefexample_function(a,b,c=0,d=0):print("Function Body:", a, b, c, d)# Calling the decorated function with different argumentsexample_function(1, 2)example_function(3, 4, c=5)
Popular Example
import timefrom functools import wrapsdeftimer(func):@wraps(func)defwrapper(*args,**kwargs): start = time.time() result =func(*args, **kwargs) end = time.time()print(f"Execution time of {func.__name__}: {end - start} seconds")return resultreturn wrapper@timerdefadd(x,y):"""Returns the sum of x and y"""return x + y@timerdefgreet(name,message="Hello"):"""Returns a greeting message with the name"""returnf"{message}, {name}!"print(add(2, 3))print(greet("Rachel"))
The purpose of @wraps is to preserve the metadata of the original function being decorated.
Practice Item
from functools import wraps# Decorator without @wrapsdefdecorator_without_wraps(func):defwrapper(*args,**kwargs):returnfunc(*args, **kwargs)return wrapper# Decorator with @wrapsdefdecorator_with_wraps(func):@wraps(func)defwrapper(*args,**kwargs):returnfunc(*args, **kwargs)return wrapper# Original function with a docstringdeforiginal_function():""" This is the original function's docstring. """pass# Decorate the original functiondecorated_function_without_wraps =decorator_without_wraps(original_function)decorated_function_with_wraps =decorator_with_wraps(original_function)# Display metadata of decorated functionsprint("Without @wraps:")print(f"Name: {decorated_function_without_wraps.__name__}")print(f"Docstring: {decorated_function_without_wraps.__doc__}")print("\nWith @wraps:")print(f"Name: {decorated_function_with_wraps.__name__}")print(f"Docstring: {decorated_function_with_wraps.__doc__}")