You should use decorators when you want to add reusable functionality to functions or methods without modifying their original code. They are perfect for tasks like logging, access control, memoization, or performance tracking, allowing you to wrap behavior around a function.
How to use decorators in Python
Have you ever wanted to add features to a function without altering its code? What if you could enhance functions in Python effortlessly without rewriting any existing logic? Imagine you’re preparing a dish, and instead of changing the recipe, you add a seasoning to enhance the flavor. In Python, decorators achieve something similar for your code, allowing you to modify the behavior of a function without changing its original content.
Let’s explore why decorators are important and how they work.
Key takeaways:
Understanding decorators: Decorators in Python modify the behavior of functions or methods without changing their actual code. They are useful for adding extra functionality like logging, access control, or caching.
How decorators work: A decorator is a function that takes another function as input and returns a modified version of it. The decorator “wraps” the original function, adding new behavior either before or after the function runs.
Core concepts: Python allows functions to be assigned to variables, defined within other functions, and passed as arguments. These concepts are fundamental to creating decorators.
Creating decorators: Decorators can be created by defining an outer function that returns a wrapper function. This wrapper function runs the original function and can add additional behavior.
Advanced decorators: You can create decorators that accept arguments, apply multiple decorators to a single function, and use the
functools.wrapsto preserve metadata when debugging. Decorators can also handle functions with different argument types using*argsand**kwargs.Use cases: Decorators are versatile and used in scenarios like timing function executions, repeating function calls, or creating custom behavior based on decorator parameters.
How to create decorators
A decorator is a function that changes the behavior of another function without changing its actual code. Decorators work by “wrapping” the function and adding new functionality or features around it.
For example, if we have a basic function and want to log a message each time it runs. We don’t need to change the function itself. We can use a decorator to enhance the function with this additional feature.
Before learning about decorators, we’ll first understand the properties of functions. Then, we’ll go through a simple step-by-step guide to creating decorators in different cases.
Properties of functions
1. Assigning functions to a variable
One of the basic concepts behind decorators is that functions can be treated like any other variable. We can assign a function to a variable and call it using that variable.
Example
Let’s look at the code below:
def greet(name):return "Hello, {}!".format(name)say_hello = greet # Assigning function to a variableprint(say_hello("Alice")) # Output: Hello, Alice!
Try replacing “Alice" with your name and see how the program behaves when you “Run” it.
2. Defining functions inside another function
In Python, we can define a function inside another function. This is often useful when creating a decorator, as the decorator function itself contains an inner function that wraps the original function.
Example
Let’s look at the code below:
def outer_function():def inner_function():print("This is an inner function")inner_function()
3. Passing functions as arguments
Functions can also be passed as arguments to other functions. This is a key aspect of how decorators work, as a decorator takes a function as input and returns a modified version of that function.
Example
Let’s look at the code below:
def apply_function(func):return func()def say_hello():return "Educative!"print(apply_function(say_hello)) # Output: Hello!
4. Returning functions
Not only can functions be passed as arguments, but they can also return other functions. This forms the foundation of decorators, which typically return a wrapped version of the original function.
Example
Let’s look at the code below:
def outer_function():def inner_function():return "Hello Educative"return inner_functiongreeting = outer_function()print(greeting()) # Output: Hello from inner!
5. Nested functions and scope variables
Functions defined inside other functions (nested functions) have access to variables in the enclosing function’s scope. This is known as a closure and is useful when defining decorators, as it allows the decorator to maintain state or context across function calls.
Example
Let’s look at the code below:
def outer_function(message):def inner_function():print(message)return inner_functionfunc = outer_function("Hello, World!")func() # Output: Hello, World!
How decorators work
A decorator is a special function in Python that modifies another function without changing its code. Let’s start with a basic example to understand how decorators work.
1. Simple decorator
Define a function (decorator) that takes another function as its input.
Create an inner function (wrapper) that adds the extra functionality.
Return the wrapper function.
Example
Let’s look at the code below:
# Define the decoratordef simple_decorator(func):def wrapper():print("Function is about to run!")func()print("Function has finished running!")return wrapper# Apply the decorator@simple_decoratordef greet():print("Hello, World!")# Call the decorated functiongreet()
2. Multiple decorators
We can apply multiple decorators to a single function. Decorators are applied from top to bottom in the order they are defined.
Example
Let’s look at the code below:
def decorator_one(func):def wrapper():print("Decorator One")func()return wrapperdef decorator_two(func):def wrapper():print("Decorator Two")func()return wrapper@decorator_one@decorator_twodef show_message():print("Hello!")show_message()# Output:# Decorator One# Decorator Two# Hello!
3. Arguments accepted by decorators
We can also define decorators that accept arguments. This is useful when we want to create decorators with configurable behavior.
Example
Let’s look at the code below:
def decorator_with_args(times):def decorator(func):def wrapper():for _ in range(times):func()return wrapperreturn decorator@decorator_with_args(3)def say_hello():print("Hello!")say_hello()# Output:# Hello!# Hello!# Hello!
4. General-purpose decorators
We can write general-purpose decorators that work with functions of any number of arguments and keyword arguments by using *args and **kwargs.
Example
Let’s look at the code below:
def general_decorator(func):def wrapper(*args, **kwargs):print("Wrapper executed")return func(*args, **kwargs)return wrapper@general_decoratordef greet(name, age):print("Hello, my name is {} and I am {} years old".format(name, age))greet("John", 30)
5. Arguments passing to the decorator
If we want the decorator to take arguments, we can add another level of nesting to the decorator function. This allows customization when the decorator is applied.
Example
Let’s look at the code below:
def repeat(num_times):def decorator(func):def wrapper(*args, **kwargs):for _ in range(num_times):result = func(*args, **kwargs)return resultreturn wrapperreturn decorator@repeat(4)def greet(name):print("Educative".format(name))greet("Alice")
6. Debug decorators
When debugging decorators, it’s important to preserve the original function’s metadata like its name and docstring. Python’s functools.wraps can be used to retain this information in the wrapper function.
Example
Let’s look at the code below:
import functoolsdef debug_decorator(func):@functools.wraps(func)def wrapper(*args, **kwargs):print("Calling {}".format(func.__name__)) # Use .format() instead of f-stringsreturn func(*args, **kwargs)return wrapper@debug_decoratordef say_hello():print("Hello!")say_hello()
Let’s take a quiz below:
What is the primary purpose of using a decorator in Python?
To modify the underlying code of a function directly
To change the function’s behavior without altering its code
To execute the function only once
Ready to deepen your Python knowledge? check out our “Become a Python Developer” path, beginning with Python’s basic concepts and advancing to more complex topics, preparing you for a career as a Python developer.
Frequently asked questions
Haven’t found what you were looking for? Contact Us