🍔 Decorators¶
1. Introduction to Decorators¶
In Python, decorators are a design pattern that allow you to add new functionality to an existing object without modifying its structure. Decorators are typically used to extend the behavior of functions and methods, and they provide a flexible alternative to subclassing for extending functionality.
2. Basic Function¶
Before we talk about decorators, let's first understand functions in Python. Here's a simple function:
def greet(name):
return f"Hello, {name}!"
greet("Alice") # prints: Hello, Alice!
'Hello, Alice!'
3. Functions as First-Class Objects¶
In Python, functions are first-class objects. This means that functions can be passed around and used as arguments, just like any other object (string, int, float, list, and so on). Here's an example:
def greet(name):
return f"Hello, {name}!"
def call_func(func, name):
return func(name)
call_func(greet, "Alice") # prints: Hello, Alice!
'Hello, Alice!'
In this example, call_func
takes a function and a name, and it calls the provided function with the provided name.
4. Defining a Decorator¶
A decorator is just a function that takes a function as an argument, and returns a new function that usually extends or modifies the behavior of the input function. Here's an example:
def my_decorator(func):
def wrapper(name):
return f"{func(name)} How are you?"
return wrapper
@my_decorator
def greet(name):
return f"Hello, {name}!"
greet("Alice") # prints: Hello, Alice! How are you?
'Hello, Alice! How are you?'
In this code, my_decorator
is a function that takes a function func
, and returns a new function wrapper
that adds some behavior to func
.
The @my_decorator
line is Python's decorator syntax. It's just a shorthand for greet = my_decorator(greet)
.
When we call greet("Alice")
, we're actually calling the wrapper
function defined inside my_decorator
. This wrapper
function calls greet("Alice")
, then adds " How are you?"
to the end of the string that greet
returns.
5. Using Multiple Decorators¶
You can use multiple decorators for a single function. The decorators are applied from bottom to top.
def add_exclamation(func):
def wrapper(name):
return f"{func(name)}!"
return wrapper
def add_greeting(func):
def wrapper(name):
return f"Hello, {func(name)}"
return wrapper
@add_exclamation
@add_greeting
def get_name(name):
return name
get_name("Alice") # prints: Hello, Alice!
'Hello, Alice!'
In this code, get_name
is first decorated with add_greeting
(which adds "Hello, "
to the start of the string), then it's decorated with add_exclamation
(which adds "!"
to the end of the string).
6. Decorators with Arguments¶
If you want to create a decorator that takes its own arguments, you can do so by creating a function that returns a decorator.
def repeat(num_times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(num_times=4)
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
Hello, Alice! Hello, Alice! Hello, Alice! Hello, Alice!
In this code, repeat(num_times=4) returns the decorator function, which in turn takes a function and returns a wrapper that calls the given function num_times times.
So when we call greet("Alice"), it's actually calling the wrapper function which prints Hello, Alice! four times.
Decorators can be a bit confusing at first, but once you understand that a decorator is just a function that takes a function and returns a new function, they become much easier to understand. Just remember that the @decorator syntax is just a shorthand, and that you can always replace @decorator def func(args): with func = decorator(func).