Back to Python
2026-02-286 min read

Python Decorators

Learn Python Decorators step by step with clear examples and exercises.

Why This Matters

In this full guide on Python decorators, we delve deep into understanding their significance, benefits, and practical applications for your Python programming journey. Decorators are essential tools that help you write cleaner, more efficient, and easier-to-maintain code, especially when working with reusable and modular functions. They are also valuable assets during interviews or real-world coding challenges.

Prerequisites

To fully grasp this guide, it is essential to have a strong foundation in the following Python concepts:

  1. Basic Python syntax (variables, data types, operators)
  2. Functions and function definitions in Python
  3. Understanding classes and objects in Python
  4. Familiarity with control flow structures such as loops and conditional statements
  5. Comprehension of error handling techniques like exceptions
  6. Adequate understanding of modules and packages
  7. Knowledge of file I/O operations (reading and writing files)

Core Concept

What are Decorators?

A decorator is a powerful feature in Python that allows you to add additional functionality to an existing function or method, without modifying its source code directly. This is achieved by wrapping the original function with another function, known as the decorator. By doing so, you can enhance the behavior of a function while keeping its core logic intact.

How do Decorators Work?

Decorators work by defining a higher-order function (a function that takes other functions as arguments or returns functions) with a specific syntax: the @ symbol followed by the decorator's name, which is defined as a regular function with a single argument (the decorated function). This syntax tells Python to apply the decorator to the following function.

Decorators vs Higher-Order Functions

While decorators are a special case of higher-order functions, they differ in their syntax and usage. A higher-order function can be any function that takes other functions as arguments or returns functions but doesn't necessarily use the @ syntax for decoration.

Common Decorator Uses

Decorators are used for a variety of purposes, including:

  1. Logging function calls and their arguments
  2. Timing function execution and measuring performance
  3. Implementing access control or authorization checks
  4. Caching the results of expensive functions to improve performance
  5. Adding additional validation or error handling to functions
  6. Managing resource allocation and cleanup (e.g., file I/O operations)
  7. Creating custom class methods on-the-fly
  8. Implementing aspect-oriented programming (AOP) concepts
  9. Integrating with third-party libraries or frameworks
  10. Enforcing code conventions or style guidelines

Understanding the Anatomy of a Decorator

A decorator consists of three main components:

  1. The @ symbol, which signals that a decorator is being used
  2. The decorator function, which takes the decorated function as an argument and returns a new function with added functionality
  3. The wrapper function, which is defined within the decorator and serves as the "intermediary" between the original function and the decorator

Example: A Simple Decorator

def my_decorator(function):
def wrapper(*args, **kwargs):
print("Something is happening before the function is called.")
result = function(*args, **kwargs) # Call the original function
print("Something is happening after the function is called.")
return result
return wrapper

@my_decorator
def say_hello():
print("Hello, World!")

say_hello()

In this example, my_decorator is a decorator that takes the say_hello function as an argument and returns a new function (wrapper) with additional functionality. The @my_decorator syntax tells Python to apply the decorator to the say_hello function before it's called.

Advanced Decorators: Multiple Arguments, Context Managers, and Class Decorators

Decorators can be made more versatile by accepting multiple arguments, implementing context managers, or being used with classes. These advanced decorator techniques will help you tackle complex problems and create even more powerful tools for your Python toolbox.

Worked Example

Let's create a simple decorator that logs the time taken by a function to execute:

import time

def timer(function):
def wrapper(*args, **kwargs):
start_time = time.time()
result = function(*args, **kwargs)
end_time = time.time()
print(f"Function '{function.__name__}' took {end_time - start_time:.4f} seconds to execute.")
return result
return wrapper

@timer
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n-1)

factorial(5)

In this example, the timer decorator logs the time taken by the factorial function to calculate the factorial of a given number.

Common Mistakes

  1. Forgetting to define the wrapper function within the decorator:
def my_decorator(function):
def wrapper(*args, **kwargs):
print("Before function call.")
function(*args, **kwargs) # Forgetting to return wrapper will result in a SyntaxError.
my_decorator(say_hello) # This will result in a NameError because wrapper is not defined.
  1. Not returning the wrapper function from the decorator:
def my_decorator(function):
def wrapper(*args, **kwargs):
print("Before function call.")
function(*args, **kwargs) # Forgetting to return wrapper will result in a SyntaxError.
my_decorator(say_hello) # This will result in a NameError because wrapper is not defined.
  1. Applying the decorator incorrectly:
@my_decorator(say_hello) # This will result in a TypeError because my_decorator is not callable.
  1. Incorrectly using the @ symbol with class methods:
class MyClass:
@my_decorator
def my_method(self):
pass # This will result in a SyntaxError because my_method is not a standalone function.
  1. Forgetting to handle exceptions within the decorator:
def my_decorator(function):
def wrapper(*args, **kwargs):
try:
result = function(*args, **kwargs)
except Exception as e:
print(f"An error occurred in '{function.__name__}': {e}")
return result
my_decorator(say_hello) # This will not log any errors if say_hello raises an exception.

Practice Questions

  1. Write a decorator that logs the arguments passed to a function.
  2. Create a decorator that caches the results of expensive functions to improve performance.
  3. Implement a decorator that checks if the user has permission to access a protected function.
  4. Write a decorator that validates input arguments for a function, raising an exception if they are invalid.
  5. Write a decorator that times the execution of a function and returns the result only if it takes less than a specified maximum time (e.g., 1 second).
  6. Implement a decorator that automatically saves the results of a function to a file.
  7. Create a decorator that generates a unique name for each instance of a class, using a counter within the decorator.
  8. Write a decorator that ensures a function is called only once during the lifetime of the program.
  9. Implement a decorator that logs all calls to a specific module or package.
  10. Create a decorator that automatically tests a function using a testing framework like unittest.

FAQ

  1. What happens when a decorated function is called? The original function is executed after the wrapper function defined within the decorator has been run.
  2. Can I apply multiple decorators to the same function? Yes! Python allows you to apply multiple decorators by listing them in the order you want them to be applied, separated by commas.
  3. What if I need to pass arguments to my decorator? You can define your decorator to accept one or more arguments, which can then be used within the wrapper function to customize its behavior.
  4. How do I remove a decorator from a function? To remove a decorator from a function, you would need to rewrite the original function without the decorator and replace it in your code. However, this is generally not recommended as it can lead to inconsistencies or bugs in your code.
  5. Can I use decorators with classes and class methods? Yes! Decorators can be used with classes and class methods by defining them as functions that take a class or method as an argument instead of a standalone function.
  6. How do I handle exceptions within a decorator? You can use try-except blocks to catch and handle exceptions raised by the decorated function or any part of its execution, including the wrapper function itself.
  7. Can decorators be used for aspect-oriented programming (AOP)? Yes! Decorators are an essential tool in implementing aspect-oriented programming concepts in Python. They allow you to add cross-cutting concerns (e.g., logging, caching, or security) to your code without modifying the original functions directly.
  8. Are decorators limited to functions and methods? No! Decorators can be used with any callable objects, including classes, class methods, and even other decorators. This flexibility makes them a powerful tool for enhancing Python's functionality in various contexts.
Python Decorators | Python | XQA Learn