π€Decorators
In Python, a decorator is a function that takes another function as input and returns a new function as output, usually with some additional functionality. The decorator function can modify the behavior of the input function without modifying its source code.
Decorators provide a flexible and powerful way to extend the behavior of functions. They can be used to add functionality like timing, logging, validation, authorization, caching, and more. Decorators are a widely used concept in Python programming, and many built-in functions and modules use decorators.
In Python, decorators are denoted by the @
symbol followed by the decorator function name placed above the function definition. When a decorated function is called, it is replaced by the new function returned by the decorator.
Creating Decorators
In Python, there are two main ways to create decorators: function-based decorators and class-based decorators.
Function-Based Decorators:
Function-based decorators are the simplest type of decorators. They are created using a function that takes another function as input and returns a new function as output, usually with additional functionality. Here's an example:
In this example, the my_decorator
function is a decorator that takes another function (func
) as input and returns a new function (wrapper
) as output. The wrapper
function adds functionality before and after calling the original function (func
). The @my_decorator
syntax applies the decorator to the my_function
function, and when my_function()
is called, it prints the output with the added functionality.
Class-Based Decorators:
Class-based decorators are created using a class that defines a __call__
method. The __call__
method is called whenever the decorated function is called. Here's an example:
In this example, the my_decorator
class is a decorator that takes another function (func
) as input and defines a __call__
method that adds functionality before and after calling the original function. The __init__
method initializes the decorator with the original function. The @my_decorator
syntax applies the decorator to the my_function
function, and when my_function()
is called, it prints the output with the added functionality.
Both function-based and class-based decorators are widely used in Python programming to add additional functionality to functions, methods, and classes.
Examples of Decorators
Here are some examples of decorators in Python:
Timing Function Execution:
A common use case for decorators is to time the execution of a function. Here's an example:
In this example, the timer
decorator measures the execution time of the decorated function. The wrapper
function starts the timer before calling the original function (func
), ends the timer after the function finishes, calculates the execution time, and prints it. The @timer
syntax applies the decorator to the some_function
function, and when some_function()
is called, it prints the execution time.
Logging Function Calls:
Another common use case for decorators is to log the calls to a function. Here's an example:
In this example, the logger
decorator logs the calls to the decorated function. The wrapper
function prints the name of the function (func.__name__
) and the arguments passed to it (args
and kwargs
). The @logger
syntax applies the decorator to the some_function
function, and when some_function(2, 3)
is called, it prints the log message and returns the sum of x
and y
.
Checking Function Arguments:
Decorators can also be used to check the validity of the arguments passed to a function. Here's an example:
In this example, the validate
decorator checks that all the arguments passed to the decorated function are integers. If any argument is not an integer, it raises a TypeError
. The @validate
syntax applies the decorator to the some_function
function, and when some_function(2, 3)
is called, it returns the sum of x
and y
. When some_function(2, "3")
is called, it raises a TypeError
because "3"
is not an integer.
These are just a few examples of how decorators can be used to extend the behavior of functions in Python. With decorators, you can create reusable and customizable functionality that can be applied to any function in your codebase.
Chaining Multiple Decorators
In Python, it's possible to chain multiple decorators together to apply multiple levels of functionality to a function. To chain decorators, simply apply them one after the other, from top to bottom.
Here's an example of chaining multiple decorators:
In this example, the logger
and timer
decorators are chained together to create a new function that logs the calls to the decorated function and measures its execution time. The @logger
decorator is applied first, followed by the @timer
decorator. When some_function(2, 3)
is called, it prints the log message and the execution time, and returns the sum of x
and y
.
It's important to note that the order of the decorators matters when chaining them together. In the example above, the logger
decorator is applied first, which means that it wraps the timer
decorator. This is why the log message is printed before the execution time. If you switch the order of the decorators, the behavior of the function will change accordingly.
Decorators with Arguments
In Python, decorators can also take arguments, which allows you to customize their behavior. There are two types of decorators with arguments: decorators with fixed arguments and decorators with variable arguments.
Decorators with Fixed Arguments:
Decorators with fixed arguments take a fixed number of arguments that are passed to the decorator when it is applied to the decorated function. Here's an example:
In this example, the repeat
decorator takes a fixed argument (num
) that specifies the number of times the decorated function should be repeated. The decorator
function is the actual decorator that takes the decorated function (func
) as input and returns a new function (wrapper
) as output. The wrapper
function repeats the original function num
times and returns the result. The @repeat(num=3)
syntax applies the decorator to the say_hello
function, and when say_hello("John")
is called, it prints the output three times.
Decorators with Variable Arguments:
Decorators with variable arguments take a variable number of arguments using the *args
and/or **kwargs
syntax. Here's an example:
In this example, the log_params
decorator takes a variable number of arguments using the *args
and **kwargs
syntax. The wrapper
function logs the arguments and keyword arguments passed to the decorated function, and returns the result of calling the original function. The @log_params
syntax applies the decorator to the some_function
function, and when some_function(1, 2, z=3)
is called, it prints the log message and returns the sum of x
, y
, and z
.
Decorators with arguments are a powerful tool for creating flexible and customizable decorators in Python. By taking arguments, decorators can be adapted to a wide variety of use cases and can provide fine-grained control over the behavior of the decorated function.
Decorating Functions with Parameters
Decorating functions with parameters can be done using decorators that take variable arguments. To decorate a function with parameters, you need to modify the decorator function to accept arguments and pass those arguments to the wrapper function that is returned by the decorator.
Here's an example of a decorator that takes an argument and can be used to decorate functions with parameters:
In this example, the debug_args
decorator takes a fixed argument (debug
) that specifies whether to print the arguments and keyword arguments passed to the decorated function. The decorator
function takes the decorated function (func
) as input and returns a new function (wrapper
) as output. The wrapper
function checks the value of debug
and prints the arguments and keyword arguments if it is True
. The @debug_args(debug=True)
syntax applies the decorator to the some_function
function, and when some_function(1, 2, z=3)
is called, it prints the log message and returns the sum of x
, y
, and z
.
By passing arguments to decorators, you can create powerful and flexible decorators that can be customized to fit a wide range of use cases.
Last updated