ProgrammingWorld

Python Decorators Explained with Simple Examples

13 January 2025

Title image

Photo by Prashant on Unsplash

Decorators are a unique and powerful feature of Python that enable programmers to modify or extend the behavior of functions and methods. If you're a Python developer aiming to write clean, efficient, and reusable code, understanding decorators is essential. While they might seem advanced at first, decorators can be simplified with proper explanation and examples.

In this comprehensive guide, we'll break down decorators into easily digestible sections, supported by clear examples.

What Are Python Decorators?

In Python, a decorator is a function that takes another function or method as its input, adds some functionality to it, and returns a modified version of that function. Decorators are often described as "wrapping" a function with additional behavior.

How Decorators Work

When you apply a decorator, it doesn’t modify the original function directly. Instead, it replaces the original function with a new one that includes the additional functionality.

Why Use Decorators?

Decorators streamline your code by making it:

  1. Reusable: You can reuse the same functionality across multiple functions.

  2. Readable: They clarify the purpose of the modifications at a glance.

  3. Modular: Separate concerns by keeping additional logic outside the original function.

  4. Practical: Frequently used for tasks like logging, authentication, memoization, and more.

Basic Syntax of a Decorator

Here’s a simple example of a decorator:

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

Now, let's apply this decorator to a function:

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

say_hello()

Output:

Something is happening before the function is called.
Hello!
Something is happening after the function is called.

How Decorators Work Behind the Scenes

The @ symbol is syntactic sugar for applying a decorator to a function. The code:

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

is equivalent to:

def say_hello():
    print("Hello!")

say_hello = my_decorator(say_hello)

Types of Decorators

1. Function Decorators

These are the most commonly used decorators, applied to functions.

Example: Logging with Function Decorators
def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"Calling function: {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@log_decorator
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")

2. Method Decorators

Method decorators work similarly but are applied to methods inside classes.

Example: Logging Method Calls
def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"Method {func.__name__} called.")
        return func(*args, **kwargs)
    return wrapper

class Greeter:
    @log_decorator
    def greet(self, name):
        print(f"Hello, {name}!")

greeter = Greeter()
greeter.greet("Bob")

Real-World Use Cases of Python Decorators

1. Logging

Decorators are often used to log function calls.

def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"Function '{func.__name__}' was called with arguments: {args} {kwargs}")
        result = func(*args, **kwargs)
        print(f"Function '{func.__name__}' returned: {result}")
        return result
    return wrapper

@log_decorator
def add(a, b):
    return a + b

add(3, 5)

2. Authentication

Ensure users have the correct permissions to access certain functionality.

def require_admin(func):
    def wrapper(user):
        if user != "admin":
            print("Access denied!")
            return
        return func(user)
    return wrapper

@require_admin
def view_dashboard(user):
    print(f"Welcome to the dashboard, {user}!")

view_dashboard("guest")  # Output: Access denied!
view_dashboard("admin")  # Output: Welcome to the dashboard, admin!

3. Measuring Execution Time

Measure how long a function takes to execute.

import time

def timer_decorator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"Function '{func.__name__}' executed in {end_time - start_time:.4f} seconds.")
        return result
    return wrapper

@timer_decorator
def slow_function():
    time.sleep(2)
    print("Finished sleeping!")

slow_function()

Using functools.wraps

When writing decorators, the metadata of the original function (e.g., its name and docstring) is replaced by the wrapper function's metadata. Use functools.wraps to preserve it.

from functools import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("Wrapper function executed.")
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def example_function():
    """This is an example function."""
    print("Original function executed.")

print(example_function.__name__)  # Output: example_function
print(example_function.__doc__)   # Output: This is an example function.

Decorator Chaining

You can apply multiple decorators to a single function.

def decorator1(func):
    def wrapper():
        print("Decorator 1 applied.")
        return func()
    return wrapper

def decorator2(func):
    def wrapper():
        print("Decorator 2 applied.")
        return func()
    return wrapper

@decorator1
@decorator2
def my_function():
    print("Original function.")

my_function()

Output:

Decorator 1 applied.
Decorator 2 applied.
Original function.

Class-Based Decorators

Decorators can also be implemented as classes by defining a __call__ method, making the instance callable.

Example:

class MyDecorator:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("Class-based decorator applied.")
        return self.func(*args, **kwargs)

@MyDecorator
def my_function():
    print("Original function executed.")

my_function()

Common Mistakes and How to Avoid Them

  1. Not Using functools.wraps: Forgetting to preserve metadata can make debugging harder.

  2. Overusing Decorators: Keep your decorators simple and avoid nesting them unnecessarily.

  3. Ignoring *args and **kwargs: Always include *args and **kwargs in your wrapper function to handle various arguments.

Conclusion

Decorators are an elegant way to enhance the functionality of your Python code without altering its core logic. By understanding the basics and practicing with examples, you can harness their power to create more reusable, modular, and readable code.

Whether you're adding logging, authentication, or timing to your functions, decorators provide a clean and effective solution. Start with simple examples and gradually explore advanced use cases like decorator chaining and class-based decorators to master this essential Python feature.

Happy coding!

Powered by wisp

Loading...
Related Posts
Python Functions: Tips for Writing Clean and Reusable Code

Python Functions: Tips for Writing Clean and Reusable Code

Python functions are essential for writing clean, reusable, and maintainable code. This blog shares practical tips on how to write efficient functions, including proper naming conventions, avoiding redundancy, and leveraging Python's flexibility with default parameters, *args, and **kwargs. Perfect for developers aiming to improve their coding practices.

Read
Python Logging: How to Track and Debug Your Code Efficiently

Python Logging: How to Track and Debug Your Code Efficiently

Logging is an essential tool for tracking and debugging Python applications. This blog explains the basics of Python's logging module, including setup, log levels, and formatting. Learn how to integrate logging into your projects to monitor behavior, debug errors, and improve maintainability.

Read
Python Metaclasses: What Are They and When to Use Them?

Python Metaclasses: What Are They and When to Use Them?

Metaclasses in Python allow you to control the behavior of classes. This blog explains what metaclasses are, how they work, and when to use them. Learn about their role in customizing class creation and implementing design patterns to write cleaner, more flexible code.

Read
© ProgrammingWorld 2025
PrivacyTerms