In Python, decorators are a powerful tool that allow us to modify or enhance the behavior of functions or methods without changing their actual code. A decorator is essentially a function that takes another function as an argument, adds some kind of functionality, and then returns a new function.
Decorators in Python
Decorators are one of Python’s most powerful tools. It enables us to modify or enhance the behavior of functions or methods without permanently altering their code. They are widely used in frameworks like Flask and Django for tasks such as logging, authentication, and caching.
A decorator is essentially a higher-order function that takes another function as an argument, enhances or modifies it, and returns a new function with the added functionality. This allows us to extend or alter a function’s behavior without directly changing its source code. Think of a decorator as a wrapper around the original function.
Basic Syntax and Working of Decorator Function
A decorator is applied using the @decorator_name
syntax above a function definition.
def shbytes_decorator(func): # function takes another function as argument
def wrapper(): # wrapper function which calls the argument function in it
print("Do something before the function")
func() # argument function called
print("Do something after the function")
return wrapper
@shbytes_decorator # @ decorator function
def say_shbytes(): # function used as argument for decorator function
print("shbytes.com")
say_shbytes() # call to the function, which calls decorator function
- In this example, the
@shbytes_decorator
modifies the behavior of thesay_shbytes
function by adding extra print statements before and after it runs. shbytes_decorator(func)
is a decorator function which takes another function as an argument and returns a wrapper function
Working of Decorator Function
- A function is passed to a decorator – The function that we want to extend is passed to the decorator. in our example,
say_shbytes()
is the function which is passed to the decoratorshbytes_decorator(func)
. - Wrapper function – The decorator wraps the original function inside another function (
wrapper
in this case), which can add functionality. In our examplewrapper()
is the wrapper function. - Return a new function – The wrapper function is returned, replacing the original function with the new one that has the extended behavior.
Decorators and Functions with Arguments
Decorators can also accept arguments, making them more versatile.
# Decorators and Functions with Arguments
def repeat(num_times): # decorator takes argument number of times function to called
def decorator(func): # decorator function takes function as an argument
def wrapper(*args, **kwargs): # wrapper function which takes arguments
for _ in range(num_times): # loop for the number of times
func(*args, **kwargs) # function with arguments called in loop
return wrapper # return modified wrapper function
return decorator
@repeat(num_times=3) # @ decorator with num_times argument 3
def introduction(name): # function with arguments, on which decorator is used
print(f"Courses@{name}")
introduction("shbytes.com") # call the actual function
- In this example, the
repeat
decorator allows us to specify how many times the function should be called. - Function arguments are passed using the
*args
for any number of value arguments and**kwargs
for any number of key-value pair arguments.
Program Output
Courses@shbytes.com
Courses@shbytes.com
Courses@shbytes.com
Chaining Multiple Decorators
We can apply more than one decorator to a function by stacking them.
# Chaining Multiple Decorators
def bold_decorator(func): # decorator takes function as an argument
def wrapper(): # wrapper function to add <b> tag
return f"<b>{func()}</b>"
return wrapper # return wrapper function
def italic_decorator(func): # decorator takes function as an argument
def wrapper(): # wrapper function to add <i> tag
return f"<i>{func()}</i>"
return wrapper # return wrapper function
@bold_decorator # bold_decorator applied on function
@italic_decorator # italic_decorator applied on function
def say_shbytes(): # multiple decorators (chaining) applied on function
return "shbytes.com"
print(say_shbytes()) # call to the actual function
# Output => <b><i>shbytes.com</i></b>
- In this example, we have created two decorators –
bold_decorator
anditalic_decorator
. - Both decorators are applied on
say_shbytes()
function. - Order of decorators applied – The function
say_shbytes
is first passed toitalic_decorator
, and the result is then passed tobold_decorator
.
Class-Based Decorators
Decorators can also be implemented using classes. This is useful when we need more state or functionality than simple functions can provide. This is achieved providing implementation to __call__ function. Class that implements the __call__
method, making instances of the class callable like a function.
# Class-Based Decorators
class ShbytesDecorator: # decorator class
def __init__(self, func): # initialize function
self.func = func
def __call__(self): # __call__ makes class instance callable like a function
print("Before the function call")
self.func()
print("After the function call")
@ShbytesDecorator # class decorator used over a function
def say_shbytes():
print("shbytes.com")
say_shbytes()
In this example, ShbytesDecorator
is a class that implements the __call__
method. It makes instances of the class callable like a function.
Built-in Python Decorators
Python includes several built-in decorators. Some common decorators in Python are:
@staticmethod
This is used on a method in a class. It makes the method static which can be called using the class itself. We don’t need the class object to call that method. It does not access or modify the class object (i.e., no self
parameter).
class ShbytesClass:
@staticmethod # makes method static
def say_shbytes(): # static method in Shbytes class
print("shbytes.com")
ShbytesClass.say_shbytes() # calling method using class name
# Output => shbytes.com
@classmethod
This also makes method a class level method and can be called using the class name. But this also provides features to update the class attributes, state of the class etc. It has access to self
parameter.
class ShbytesClass:
@classmethod # makes method class level method
def say_shbytes(cls): # class level method
print(f"Hello from {cls}")
ShbytesClass.say_shbytes() # calling method using class name
# Output => Hello from <class '__main__.ShbytesClass'>
@property
Used to define getter, setter, and deleter methods in a class.
class MyClass:
def __init__(self, value):
self._value = value
@property # getter method for value property
def value(self):
return self._value
@value.setter # setter method for value property
def value(self, new_value):
self._value = new_value
obj = MyClass(10) # calls __init__ method
print(obj.value) # calls getter method for value property; Output => 10
obj.value = 20 # calls setter method for value property
print(obj.value) # calls getter method for value property; Output => 20
Real-World Use Cases of Decorators
Decorators are extensively used in various Python frameworks for tasks like:
- Logging – Automatically log information about function calls.
- Authentication – Check whether a user is authenticated before executing a function.
- Caching – Cache results of expensive computations to avoid redundant work.
- Performance Timing – Measure the execution time of a function.
Logging Example
# logging example
def log_function_call(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with {args}, {kwargs}")
return func(*args, **kwargs)
return wrapper
@log_function_call
def add(a, b):
return a + b
print(add(5, 10))
# Output => Calling add with (5, 10), {}; 15
Performance Timing Example
# performance timing example
import time
def calculate_execution_time(func):
def wrapper(*args, **kwargs):
start_time = time.time() # take function start time
result = func(*args, **kwargs)
end_time = time.time() # take function end time
print(f"{func.__name__} took {end_time - start_time:.4f} seconds to execute")
return result
return wrapper
@calculate_execution_time
def slow_function():
time.sleep(1)
print("Function finished")
slow_function()
# Output => Function finished; slow_function took 1.0010 seconds to execute
Summary
Python decorators provide a powerful and flexible way to enhance the functionality of functions or methods in our code. Whether we are using decorators for logging, authentication, performance tracking, or simply extending function behavior, decorators can help us write cleaner and more maintainable code.
In this article, we learned about Decorators in Python. Following topics were covered:
Code – Github Repository
All code snippets and programs for this article and for Python tutorial, can be accessed from Github repository – Comments and Docstring in Python.