Decorators are a powerful tool that allows you to modify or enhance the behavior of functions or methods without changing their actual code. They are often used to add cross-cutting concerns like logging, authentication, caching, or timing to functions.
A decorator is essentially a higher-order function that takes another function as an argument, wraps some additional functionality around it, and returns a new function that can be called in place of the original. This makes decorators a very flexible and reusable mechanism for extending the behavior of functions.
Let’s explore how decorators work by writing a simple decorator that logs the execution time of a function.
import time
# Defining the decorator
def timer_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
execution_time = end_time - start_time
print(f"Function '{func.__name__}' executed in {execution_time:.4f} seconds")
return result
return wrapper
# Using the decorator to enhance a function
@timer_decorator
def some_function(n):
total = 0
for i in range(n):
total += i
return total
# Calling the decorated function
result = some_function(1000000)
print(f"Result: {result}")
time
Moduleimport time
time
Module: We import the time
module to measure the execution time of the function. The time.time()
function returns the current time in seconds since the epoch, which is useful for timing how long operations take.def timer_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
execution_time = end_time - start_time
print(f"Function '{func.__name__}' executed in {execution_time:.4f} seconds")
return result
return wrapper
timer_decorator
Function: This function is a decorator that takes a function func
as an argument and returns a new function wrapper
that enhances func
.
wrapper
Function: This inner function wraps the original func
. It takes any positional and keyword arguments (*args, **kwargs
) that func
might need.wrapper
function starts by recording the current time using start_time = time.time()
.func(*args, **kwargs)
and stores its result in result
.end_time = time.time()
.start_time
from end_time
.func.__name__
) and the time it took to execute, formatted to four decimal places.wrapper
returns the result of the original function call. This ensures that the decorated function behaves just like the original, but with additional logging.@timer_decorator
def some_function(n):
total = 0
for i in range(n):
total += i
return total
@timer_decorator
: This syntax is a shorthand for applying the timer_decorator
to some_function
. It’s equivalent to writing some_function = timer_decorator(some_function)
. The @
symbol is used to apply the decorator directly above the function definition.some_function
: This is a simple function that sums up all integers from 0 to n-1
. After decorating with @timer_decorator
, whenever some_function
is called, it will automatically be wrapped by the timer_decorator
, and its execution time will be logged.result = some_function(1000000)
print(f"Result: {result}")
Executing the Function: When some_function(1000000)
is called, it runs the decorated version of the function. The wrapper
function times the execution, prints the time taken, and then returns the result.
Output: The result of the sum is printed, and the execution time is logged to the console. For example:
Function 'some_function' executed in 0.0483 seconds
Result: 499999500000