Back to Python

Python Decorators

Introduction to Decorators

Decorators are a powerful and expressive feature in Python that allow you to modify or extend the behavior of functions or classes without permanently modifying them.

# Basic decorator example
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

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

# Calling the decorated function
say_hello()
# Output:
# Something is happening before the function is called.
# Hello!
# Something is happening after the function is called.

Decorators are essentially functions that take another function as an argument and return a modified function.

Decorator Syntax

Python provides a special syntax for applying decorators using the @ symbol. This is called "pie syntax".

# The @decorator syntax is equivalent to:
def original_function():
    pass

# These two are equivalent:
decorated_function = my_decorator(original_function)
@my_decorator
def original_function():
    pass

# Decorators can be stacked:
@decorator1
@decorator2
def my_function():
    pass
# Equivalent to: my_function = decorator1(decorator2(my_function))

The decorator syntax makes your code cleaner and more readable by keeping the decoration close to the function definition.

Decorators with Arguments

Decorators can accept arguments by adding another layer of nesting to the decorator function.

# Decorator with arguments
def repeat(num_times):
    def decorator_repeat(func):
        def wrapper(*args, **kwargs):
            for _ in range(num_times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator_repeat

@repeat(num_times=3)
def greet(name):
    print(f"Hello {name}")

greet("Alice")
# Output:
# Hello Alice
# Hello Alice
# Hello Alice

This pattern allows decorators to be customized when they're applied, making them more flexible and reusable.

Class Decorators

Decorators can also be applied to classes, modifying or extending their behavior.

# Class decorator example
def add_methods(cls):
    def new_method(self):
        return "This is a new method"
    cls.new_method = new_method
    return cls

@add_methods
class MyClass:
    def __init__(self, value):
        self.value = value

obj = MyClass(42)
print(obj.new_method()) # "This is a new method"

# Built-in class decorators
@dataclass
class Point:
    x: int
    y: int

p = Point(1, 2)
print(p) # Point(x=1, y=2)

Class decorators are commonly used for adding methods, modifying attributes, or implementing design patterns.

Built-in Decorators

Python comes with several useful built-in decorators for common tasks.

# @staticmethod - doesn't receive implicit first argument
class Math:
    @staticmethod
    def add(x, y):
        return x + y

# @classmethod - receives class as first argument
class Person:
    def __init__(self, name):
        self.name = name
    @classmethod
    def from_dict(cls, data):
        return cls(data['name'])

# @property - defines a getter method
class Circle:
    def __init__(self, radius):
        self._radius = radius
    @property
    def radius(self):
        return self._radius
    @radius.setter
    def radius(self, value):
        if value >= 0:
            self._radius = value
        else:
            raise ValueError("Radius must be positive")

# @functools.lru_cache - memoization decorator
from functools import lru_cache
@lru_cache(maxsize=32)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

These built-in decorators provide functionality for method types, property management, and performance optimization.

Real-world Decorator Examples

Decorators are commonly used in web frameworks, testing, and other practical applications.

# Timing decorator
import time
from functools import wraps

def timing(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"Function {func.__name__} took {end-start:.4f} seconds")
        return result
    return wrapper

@timing
def slow_function():
    time.sleep(1)

slow_function() # Prints timing information

# Flask route decorator example
from flask import Flask
app = Flask(__name__)

@app.route('/')
def home():
    return "Hello, World!"

# Login required decorator
def login_required(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        if not current_user.is_authenticated:
            return redirect(url_for('login'))
        return func(*args, **kwargs)
    return wrapper

Decorators are widely used in web frameworks (Flask, Django), testing (pytest fixtures), and many other Python libraries.

Python Decorators Videos

Master Python decorators with these handpicked YouTube tutorials:

Decorator Basics

Learn the fundamentals of Python decorators:

Advanced Decorators

Deep dive into decorator techniques:

Practical Applications

Real-world decorator use cases:

Python Decorators Quiz