A decorator is a function that allows you to modify or extend the behavior of another function without changing its original code.
Decorators are widely used in Python for logging, authentication, validation, timing, caching, and many other tasks.
A decorator is a function that wraps another function and changes or extends its behavior.
Python provides a special @ syntax for applying decorators.
def decorator(function):
def wrapper():
print("Before function")
function()
print("After function")
return wrapper
@decorator
def greet():
print("Hello")
greet()
Before function Hello After function
Decorators are useful for:
In Python, functions are objects. This means a function can be:
def greet():
print("Hello")
message = greet
message()
Hello
A function can be passed to another function as an argument.
def greet():
print("Hello")
def execute(function):
function()
execute(greet)
Hello
A function can also return another function.
def outer():
def inner():
print("Hello from inner function")
return inner
function = outer()
function()
Hello from inner function
def decorator(function):
def wrapper():
print("Start")
function()
print("End")
return wrapper
def greet():
print("Hello")
greet = decorator(greet)
greet()
Start Hello End
Python provides a shorter way to apply a decorator using the @ symbol.
def decorator(function):
def wrapper():
print("Before")
function()
print("After")
return wrapper
@decorator
def greet():
print("Hello")
greet()
Before Hello After
The statement @decorator is essentially a convenient way of applying the decorator to the function.
If the decorated function accepts arguments, the wrapper should usually accept them as well.
def decorator(function):
def wrapper(name):
print("Before function")
function(name)
print("After function")
return wrapper
@decorator
def greet(name):
print("Hello", name)
greet("Rahul")
Before function Hello Rahul After function
To create a decorator that works with functions having different arguments, use *args and **kwargs.
def decorator(function):
def wrapper(*args, **kwargs):
print("Function is running")
return function(*args, **kwargs)
return wrapper
@decorator
def add(a, b):
return a + b
print(add(10, 20))
Function is running 30
A decorator should return the result of the original function when the result needs to be preserved.
def decorator(function):
def wrapper(*args, **kwargs):
result = function(*args, **kwargs)
return result
return wrapper
@decorator
def multiply(a, b):
return a * b
print(multiply(5, 4))
20
A decorator can be used to print information whenever a function is called.
def logger(function):
def wrapper(*args, **kwargs):
print("Calling function:", function.__name__)
result = function(*args, **kwargs)
return result
return wrapper
@logger
def greet():
print("Hello")
greet()
Calling function: greet Hello
Decorators can be used to measure how long a function takes to execute.
import time
def timer(function):
def wrapper(*args, **kwargs):
start = time.time()
result = function(*args, **kwargs)
end = time.time()
print("Time:", end - start)
return result
return wrapper
@timer
def calculate():
total = 0
for i in range(100000):
total += i
return total
calculate()
The decorator measures the approximate execution time of calculate().
When a decorator replaces a function with a wrapper, metadata such as the original function's name and documentation can otherwise be lost.
The functools.wraps decorator helps preserve that metadata.
from functools import wraps
def decorator(function):
@wraps(function)
def wrapper(*args, **kwargs):
return function(*args, **kwargs)
return wrapper
@decorator
def greet():
"""Display a greeting."""
print("Hello")
print(greet.__name__)
print(greet.__doc__)
greet Display a greeting.
Decorators can be used to check whether a user is allowed to execute a function.
def login_required(function):
def wrapper(logged_in):
if logged_in:
return function()
else:
print("Please login first")
return wrapper
@login_required
def dashboard():
print("Welcome to dashboard")
dashboard(True)
dashboard(False)
Welcome to dashboard Please login first
A decorator can validate function arguments before calling the original function.
def positive_only(function):
def wrapper(number):
if number > 0:
return function(number)
print("Number must be positive")
return wrapper
@positive_only
def square(number):
print(number * number)
square(5)
square(-2)
25 Number must be positive
More than one decorator can be applied to a function.
def decorator_one(function):
def wrapper():
print("Decorator One")
function()
return wrapper
def decorator_two(function):
def wrapper():
print("Decorator Two")
function()
return wrapper
@decorator_one
@decorator_two
def greet():
print("Hello")
greet()
Decorator One Decorator Two Hello
Decorators are applied from the bottom upward in this example.
A decorator itself can also be configured using parameters. This requires another level of function nesting.
def repeat(times):
def decorator(function):
def wrapper():
for i in range(times):
function()
return wrapper
return decorator
@repeat(3)
def greet():
print("Hello")
greet()
Hello Hello Hello
Consider this code:
@decorator
def greet():
print("Hello")
Python effectively performs an operation equivalent to:
def greet():
print("Hello")
greet = decorator(greet)
The original function is passed to the decorator and the returned function becomes the new value of greet.
Decorators commonly use nested functions. The inner wrapper function can access the function passed by the outer decorator.
def decorator(function):
def wrapper():
print("Before")
function()
return wrapper
Here, wrapper() remembers the function provided to decorator().
This is related to Python's concept of closures.
Python provides several decorators and decorator-like tools. Some commonly used examples are:
@property@classmethod@staticmethod@abstractmethodclass Student:
@staticmethod
def school_name():
return "ABC School"
print(Student.school_name())
ABC School
| Use | Purpose |
|---|---|
| Logging | Record function calls and related information. |
| Authentication | Check whether a user has permission. |
| Validation | Check input before executing a function. |
| Timing | Measure execution time. |
| Caching | Reuse previously calculated results. |
| Access Control | Control access to functionality. |
from functools import wraps
def log_function(function):
@wraps(function)
def wrapper(*args, **kwargs):
print("Function started")
result = function(*args, **kwargs)
print("Function completed")
return result
return wrapper
@log_function
def add(a, b):
return a + b
result = add(10, 20)
print("Result:", result)
Function started Function completed Result: 30
@decorator syntax.*args and **kwargs help decorators support flexible arguments.functools.wraps helps preserve function metadata.@property, @classmethod and @staticmethod.Question: Which symbol is used to apply a decorator to a function?