Decorators in Python are a powerful feature that allows modifying the behavior of a function or a class without changing its source code. They provide a way to wrap a function or a class with another function or class, allowing the addition of new functionalities or changing the way the original function or class works.
The syntax for using decorators is to place the “@” symbol followed by the name of the decorator function above the function that is to be decorated. When the decorated function is called, the decorator function is also called, and it can perform some actions before and/or after the call to the decorated function.
Here is an example of a decorator function that takes a function as an argument, prints a message before the function is called, calls the function, and prints a message after the function returns:
def my_decorator(func):
def wrapper():
print("Before the function is called.")
func()
print("After the function is called.")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
When the say_hello
function is called, it is wrapped by the my_decorator
function. The wrapper
function is returned and assigned to the say_hello
function name. So when say_hello
is called, it actually calls the wrapper
function, which first prints "Before the function is called.", then calls the original say_hello
function that prints "Hello!", and finally prints "After the function is called."
Decorators can be used for a variety of purposes, such as logging, measuring performance, authentication, memoization, and more. Decorators can also be chained together by applying multiple decorators to a single function or class.
def my_decorator1(func):
def wrapper():
print("Before my_decorator1 is called.")
func()
print("After my_decorator1 is called.")
return wrapper
def my_decorator2(func):
def wrapper():
print("Before my_decorator2 is called.")
func()
print("After my_decorator2 is called.")
return wrapper
@my_decorator1
@my_decorator2
def say_hello():
print("Hello!")
say_hello()
In this example, the say_hello
function is decorated with both my_decorator1
and my_decorator2
. The decorators are applied in the order they are listed, so the output will be:
Before my_decorator1 is called.
Before my_decorator2 is called.
Hello!
After my_decorator2 is called.
After my_decorator1 is called.
In addition to function decorators, Python also has class decorators, which are used to modify the behavior of a class or its methods. Here’s an example of a class decorator that adds a new method to a class:
def my_class_decorator(cls):
class NewClass(cls):
def new_method(self):
print("This is a new method.")
return NewClass
@my_class_decorator
class MyClass:
def old_method(self):
print("This is an old method.")
my_obj = MyClass()
my_obj.old_method()
my_obj.new_method()
In this example, the my_class_decorator
function takes a class as an argument and returns a new class that inherits from the original class but adds a new method. The @my_class_decorator
decorator is applied to the MyClass
class, which results in a new class NewClass
that has the old_method
inherited from MyClass
and the new_method
added by the decorator. When the old_method
and new_method
are called on an instance of MyClass
, they will execute as expected.
Finally, Python also supports parameterized decorators, which allow you to pass arguments to the decorator function. This can be useful when you want to apply different behaviors to different functions or classes. Here’s an example of a parameterized decorator:
def my_decorator(arg1, arg2):
def inner_decorator(func):
def wrapper(*args, **kwargs):
print("Decorator arguments:", arg1, arg2)
func(*args, **kwargs)
return wrapper
return inner_decorator
@my_decorator("Hello", 42)
def my_function():
print("Inside the function.")
my_function()
In this example, the my_decorator
function takes two arguments and returns an inner decorator function. The inner decorator takes a function as an argument and returns a wrapper function that prints the decorator arguments and then calls the original function. The @my_decorator("Hello", 42)
decorator is applied to the my_function
function, passing the arguments "Hello" and 42 to the decorator. When my_function
is called, it will execute the wrapper function returned by the decorator, which will print the decorator arguments and then call the original function.
Parameterized decorators can be used to implement more complex behaviors, such as caching, logging, and input validation, among others. They provide a flexible and powerful mechanism for modifying the behavior of functions and classes in a modular way, making it easier to maintain and evolve software over time.
It’s also worth noting that decorators can be stacked, meaning that you can apply multiple decorators to a single function or class. When multiple decorators are used, they are applied from the bottom up, with the innermost decorator applied first. Here’s an example:
def my_decorator1(func):
def wrapper(*args, **kwargs):
print("Inside decorator 1.")
func(*args, **kwargs)
return wrapper
def my_decorator2(func):
def wrapper(*args, **kwargs):
print("Inside decorator 2.")
func(*args, **kwargs)
return wrapper
@my_decorator1
@my_decorator2
def my_function():
print("Inside the function.")
my_function()
In this example, the my_decorator1
and my_decorator2
decorators are applied to the my_function
function. When my_function
is called, the my_decorator2
decorator is applied first, printing "Inside decorator 2." and then calling the my_decorator1
decorator, which in turn prints "Inside decorator 1." and then calls the original function, which prints "Inside the function." The output of this code will be:
Inside decorator 1.
Inside decorator 2.
Inside the function.
Overall, Decorators are a powerful and flexible feature of Python that enable you to modify the behavior of functions and classes in a clean and modular way. They can be used to implement a wide variety of behaviors, such as caching, logging, input validation, and much more. By mastering the use of decorators, you can become a more effective and efficient Python developer.