Python Decorators: What They Are, How They Work, and Why Use Them

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

What Are Decorators in Python

If you've written Python for more than a week, you've likely seen the @ symbol above a function. That's a decorator — a powerful and elegant programming tool.

Decorators in Python let you "wrap" one function inside another. They modify or extend a function's behavior without changing its source code. Decorators are used everywhere: in Flask, Django, FastAPI, pytest, and even in Python's standard library.

How Decorators Work

Decorator Basics

A decorator is a function that takes another function and returns a new function or a modified version of the original.

def decorator(func):
    def wrapper():
        print("Before call")
        func()
        print("After call")
    return wrapper

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

say_hello()

Output:

Before call
Hello!
After call

The @decorator syntax is syntactic sugar for:

say_hello = decorator(say_hello)

How Decorators Work Internally

To understand decorators in Python, you need to know these key concepts:

  • Functions are first-class objects in Python
  • Functions can be passed as arguments to other functions
  • You can define functions inside other functions
  • Closures are a built-in mechanism

Universal Decorator Template

The minimal decorator template looks like this:

def decorator(func):
    def wrapper(*args, **kwargs):
        # Actions before the function runs
        result = func(*args, **kwargs)
        # Actions after the function runs
        return result
    return wrapper

Practical Decorator Examples

Timing Decorator

import time

def timing(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        execution_time = time.time() - start
        print(f"{func.__name__} took {execution_time:.4f} sec")
        return result
    return wrapper

@timing
def slow_func():
    time.sleep(1)
    return "Operation complete"

slow_func()

This decorator is great for profiling function performance and optimizing your code.

Authorization Decorator

def require_admin(func):
    def wrapper(user, *args, **kwargs):
        if user != "admin":
            raise PermissionError("Access denied")
        return func(user, *args, **kwargs)
    return wrapper

@require_admin
def delete_database(user):
    print("Database deleted!")
    return True

delete_database("admin")  # Successful execution

This decorator enhances application security by controlling access to critical functions.

Logging Decorator

import logging
from functools import wraps

def log_calls(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        logging.info(f"Calling {func.__name__} with args {args}, {kwargs}")
        try:
            result = func(*args, **kwargs)
            logging.info(f"Function {func.__name__} completed successfully")
            return result
        except Exception as e:
            logging.error(f"Error in {func.__name__}: {e}")
            raise
    return wrapper

Decorators with Arguments

Creating a Decorator Factory

When a decorator needs to accept parameters, you create a decorator factory:

def repeat(n):
    def decorator(func):
        def wrapper(*args, **kwargs):
            results = []                

Blogs

Book Recommendations