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 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.
defshbytes_decorator(func):# function takes another function as argumentdefwrapper():# wrapper function which calls the argument function in itprint("Do something before the function")
func()# argument function calledprint("Do something after the function")return wrapper
@shbytes_decorator# @ decorator function defsay_shbytes():# function used as argument for decorator functionprint("shbytes.com")
say_shbytes()# call to the function, which calls decorator function
In this example, the @shbytes_decorator modifies the behavior of the say_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 decorator shbytes_decorator(func).
Wrapper function – The decorator wraps the original function inside another function (wrapper in this case), which can add functionality. In our example wrapper() 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 Argumentsdefrepeat(num_times):# decorator takes argument number of times function to calleddefdecorator(func):# decorator function takes function as an argumentdefwrapper(*args,**kwargs):# wrapper function which takes argumentsfor _ inrange(num_times):# loop for the number of times
func(*args,**kwargs)# function with arguments called in loopreturn wrapper # return modified wrapper functionreturn decorator
@repeat(num_times=3)# @ decorator with num_times argument 3defintroduction(name):# function with arguments, on which decorator is usedprint(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.
We can apply more than one decorator to a function by stacking them.
# Chaining Multiple Decoratorsdefbold_decorator(func):# decorator takes function as an argumentdefwrapper():# wrapper function to add <b> tagreturnf"<b>{func()}</b>"return wrapper # return wrapper functiondefitalic_decorator(func):# decorator takes function as an argumentdefwrapper():# wrapper function to add <i> tagreturnf"<i>{func()}</i>"return wrapper # return wrapper function@bold_decorator# bold_decorator applied on function@italic_decorator# italic_decorator applied on functiondefsay_shbytes():# multiple decorators (chaining) applied on functionreturn"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 and italic_decorator.
Both decorators are applied on say_shbytes() function.
Order of decorators applied – The function say_shbytes is first passed to italic_decorator, and the result is then passed to bold_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 DecoratorsclassShbytesDecorator:# decorator classdef__init__(self, func):# initialize function
self.func = func
def__call__(self):# __call__ makes class instance callable like a functionprint("Before the function call")
self.func()print("After the function call")@ShbytesDecorator# class decorator used over a functiondefsay_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).
classShbytesClass:@staticmethod# makes method staticdefsay_shbytes():# static method in Shbytes classprint("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.
classShbytesClass:@classmethod# makes method class level methoddefsay_shbytes(cls):# class level methodprint(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.
classMyClass:def__init__(self, value):
self._value = value
@property# getter method for value propertydefvalue(self):return self._value
@value.setter# setter method for value propertydefvalue(self, new_value):
self._value = new_value
obj = MyClass(10)# calls __init__ methodprint(obj.value)# calls getter method for value property; Output => 10
obj.value =20# calls setter method for value propertyprint(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 exampledeflog_function_call(func):defwrapper(*args,**kwargs):print(f"Calling {func.__name__} with {args}, {kwargs}")return func(*args,**kwargs)return wrapper
@log_function_calldefadd(a, b):return a + b
print(add(5,10))# Output => Calling add with (5, 10), {}; 15
Performance Timing Example
# performance timing exampleimport time
defcalculate_execution_time(func):defwrapper(*args,**kwargs):
start_time = time.time()# take function start time
result = func(*args,**kwargs)
end_time = time.time()# take function end timeprint(f"{func.__name__} took {end_time - start_time:.4f} seconds to execute")return result
return wrapper
@calculate_execution_timedefslow_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: