Functions can be passed as arguments to other functions, as well as functions can be the results of other functions. This fact is used to create special functions called decorators.
A decorator is a function that allows changing the behavior of a function (received as an argument) without altering the code of the function itself. Typically, decorators are used to add additional capabilities to functions. The essence of using a decorator is that they can add the same behavior to different functions. For example, decorators widely used to compute the execution time of a function, check the correctness of function arguments, control data security during function execution, and so on.
To create a decorator, you need to create a function that receives another function as an argument, modify the received function inside the decorator's body, and return the modified function. For example, let's create a decorator called decorator that can only be applied to functions with an empty argument list.
Code: Example Decorator
def decorator(function):
def decorated_function():
print("Code - before calling the function")
# Calling a decorated function
function()
print("Code - after calling the function")
# return decorate function
return decorated_function
This code defines a decorator function called decorator that takes another function (function) as an argument. Inside the decorator, a new function called decorated_function is defined. This decorated_function wraps around the original function and adds additional behavior before and after calling it. When decorated_function is called, it first prints "Code - before calling the function", then calls the original function, and finally prints "Code - after calling the function". The decorator then returns the decorated_function, effectively replacing the original function with the decorated version. Here's a breakdown of the code:
def decorator(function):: Defines the decorator function that takes another function as an argument.
def decorated_function():: Defines the inner function within the decorator, which will wrap around the original function.
print("Code - before calling the function"): Prints a message before calling the original function.
function(): Calls the original function.
print("Code - after calling the function"): Prints a message after calling the original function.
return decorated_function: Returns the decorated function, which replaces the original function when the decorator is applied.
Code: Example Decorator
def foo():
print("Hello from function foo!")
Let's decorate it using the decorator decorator. To do this, we need to assign the result of executing the decorator decorator over the variable sayHello (which is a reference to the function in memory).
Code: Example Decorator
>>> f = decorator(foo)
>>> f()
Code - before calling the function
Hello from function foo!
Code - after calling the function
For ease of understanding the code, function decoration during their definition can be done using the @ operator. For example, the above description of the sayHello function and its decoration.
Code: Example Decorator
def foo():
print("Hello from function foo!")
f = decorator(foo)
f()
# It is possible to replace them with an equivalent syntactic construct.
@decorator
def foo():
print("Hello from function foo!")
foo()
Code Reusability: Decorators allow you to wrap common functionality around multiple functions, promoting code reuse and reducing redundancy.
Separation of Concerns: Decorators help separate concerns by allowing you to add or modify behavior without directly altering the original function's code. This promotes cleaner and more modular code.
Enhanced Readability: Decorators can make code more readable by clearly delineating cross-cutting concerns or additional functionality applied to a function.
Promotion of Single Responsibility Principle (SRP): By allowing you to separate concerns, decorators promote the SRP, which states that a function or class should have only one reason to change.
Dynamic Behavior Addition: Decorators allow you to dynamically modify the behavior of functions at runtime, enabling powerful metaprogramming techniques.
Framework Flexibility: Many Python frameworks and libraries, such as Flask and Django, heavily utilize decorators for implementing middleware, authentication, and routing, providing a flexible and expressive way to extend functionality.
Built-in Pythonic Feature: Decorators are a native feature of Python, making them a natural and idiomatic way to extend and modify code behavior. They are widely used and understood within the Python community.
Overall, decorators are a powerful tool in Python for improving code organization, readability, and flexibility.