AlgoMaster Logo

Decorators

Last Updated: January 3, 2026

5 min read

What if you could modify the behavior of your functions without changing their code?

That's where decorators come into play in Python. These nifty tools allow you to wrap a function with another function, enabling you to add functionality, modify inputs or outputs, and much more—all while keeping your function definitions clean and readable.

Decorators can seem a bit magical at first, but once you understand how they work, they open up a wealth of possibilities for cleaner, more Pythonic code. Let’s dive into the world of decorators, exploring how they work, how to create your own, and some common use cases that will make you appreciate their power.

What is a Decorator?

At its core, a decorator is a function that takes another function as an argument, adds some functionality, and returns a new function. This allows you to extend or alter a function's behavior without modifying its code directly.

To illustrate this, let’s start with a simple example:

In this code, we define a simple_decorator that adds behavior before and after the say_hello function is executed. The @simple_decorator syntax is syntactic sugar, meaning it’s a cleaner way to express that say_hello is being passed to simple_decorator.

When you run this code, the output will be:

The key takeaway here is that the original function remains unchanged, while the decorator enhances its functionality.

Creating Your Own Decorators

Now that we understand the basic concept, let’s see how to create more versatile decorators. A common use case is to pass arguments to decorators. This is achieved by adding another layer of function nesting.

Here’s how you can accomplish that:

In this example, repeat is a decorator factory that takes an argument (num_times). The inner decorator_repeat function wraps the decorated function, allowing it to execute multiple times based on the passed argument.

When you run this code, you’ll see:

This shows how decorators can be flexible and powerful, allowing us to customize their behavior based on our needs.

Practical Use Cases for Decorators

Decorators shine in a variety of real-world scenarios. Let’s explore a few practical applications that demonstrate their usefulness.

Logging

One common use case is logging function calls. If you want to track when functions are called and their parameters, decorators are an elegant solution.

Here, the log_function_call decorator records the function name, arguments, and execution time. It’s a handy tool for debugging or monitoring performance.

Access Control

Another interesting application is implementing access control. Let’s say you have a function that should only be accessed by users with a certain role.

In this example, requires_admin checks the user role before allowing access to the delete_user function. This keeps the access logic centralized and reusable across multiple functions.

Chaining Decorators

You can also chain multiple decorators together to layer behavior. This can be particularly useful if you want to combine multiple functionalities.

In this case, shout gets decorated first by repeat, which calls it twice, and then by uppercase_decorator, which changes the output to uppercase. The output will be:

Chaining decorators can help you keep your functions clean while adding layers of complexity as needed.

Common Pitfalls and Best Practices

While decorators are powerful, there are some pitfalls to be mindful of:

1. Lost Metadata

When you use decorators, the metadata of the original function (like its name and docstring) can be lost. To avoid this, you can use functools.wraps.

Using @wraps(func) helps preserve the original function’s metadata, making debugging easier.

2. Passing Arguments

Remember that if you want your decorator to handle arguments, you need to nest your functions correctly, as we demonstrated earlier. Misunderstanding this can lead to unexpected behavior.

3. Using *args and **kwargs

Always use *args and **kwargs in your wrapper functions to ensure you can handle any function signature. This flexibility is crucial for decorators to work with various functions.

Now that you have a solid grasp of decorators, you can see how they enhance function behavior efficiently.

In the next chapter, we will explore the functools module, which provides higher-order functions that can simplify many common programming tasks, especially when working with decorators.