What it is

Decorators are a way to add extra functionality to your functions without changing the original function’s code. Think of them like a wrapper that adds something extra to a function, without modifying the function itself.

To implement a decorator, you define a function that takes another function as an argument, and returns a new function that adds the desired functionality.

def handle_errors(func):
    def wrapper():
        try:
            func()
        except Exception as e:
            print(f"Error occurred: {e}")
    return wrapper
 
@handle_errors
def divide():
    result = 10 / 0
    print(result)

I think this example implementation is particularly useful, I’ve used it before to handle errors in functions without having to add try-except blocks everywhere.

You can also go a bit deeper and use decorators with parameters, create class-based decorators for more complex use cases, use built-in decorators like @staticmethod and @classmethod, stack multiple decorators on a single function, or even use functools like functools.wraps to preserve the original function’s metadata when creating decorators.

TIP

Use decorators for cross-cutting concerns like logging, timing, authentication, caching, or error handling. They help keep your core function logic clean and focused.