Decorators in Python are essentially functions that wrap other functions or methods. They allow programmers to modify or inject code in functions or methods.
Here’s a basic example of a decorator:
```
def my_decorator(function):
def wrapper():
print(“Something is happening before the function is called.”)
function()
print(“Something is happening after the function is called.”)
return wrapper
@my_decorator
def say_hello():
print(“Hello!”)
say_hello()
```
In this script above, `my_decorator` is a decorator that wraps the `say_hello` function and modifies its behavior. When `say_hello` is called, it prints “Something is happening before the function is called”, then “Hello!”, and finally “Something is happening after the function is called”.
Decorators can also take arguments:
```
def repeat(n):
def decorator(function):
def wrapper(*args, **kwargs):
for _ in range(n):
function(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def say_hello(name):
print(f“Hello {name} “)
say_hello(“Alice”)
```
In this script, `repeat` is a decorator factory that takes an argument and returns a decorator. The decorator then wraps the function and modifies its behavior.
Note that Python decorators are applied in the order they are stacked.
```
decorator1
decorator2
def function():
pass
```
In the above script, `function` is first wrapped by `decorator2`, and then the resulting callable is wrapped by `decorator1`.
To retain the metadata of the original function (such as its name, docstring, annotations), use `functools.wraps`:
```
from functools import wraps
def my_decorator(function):
@wraps(function)
def wrapper(*args, **kwargs):
# do something before the function call
result = function(*args, **kwargs)
# do something after the function call
return result
return wrapper
```
This is especially important when testing (assertions based on function’s identity might fail) or inspecting (documentation generators use the docstrings to show function’s usage).