← Back to Chapters

Python Decorators

? Python Decorators

⚡ Quick Overview

A decorator in Python is a function that takes another function and extends or modifies its behavior without changing the original function’s code. Decorators are heavily used for logging, authentication, caching, performance measurement, and many other cross-cutting concerns.

? Common Use Cases

  • Adding logging before and after function calls.
  • Checking permissions or authentication before running a function.
  • Caching results of expensive function calls.
  • Measuring execution time of functions.
  • Validating input or output in a reusable way.

? Key Concepts

  • In Python, functions are first-class objects (they can be passed as arguments, returned, stored in variables).
  • A decorator is a function that wraps another function inside an inner function (often called wrapper).
  • The @decorator_name syntax is just a shorthand for: func = decorator_name(func).
  • To handle any number of arguments, decorators usually use *args and **kwargs in the wrapper.
  • You can stack multiple decorators; the order of stacking affects the final result.

? Syntax & Theory

A basic decorator pattern looks like this:

def my_decorator(func):
    def wrapper(*args, **kwargs):
        # Do something before
        result = func(*args, **kwargs)
        # Do something after
        return result
    return wrapper

@my_decorator
def my_function():
    print("Running my_function()")

When Python sees @my_decorator above my_function, it effectively executes: my_function = my_decorator(my_function). The original my_function is replaced by the wrapper returned from the decorator.

? Code Examples

? Passing Functions as Arguments
# Define a simple function
def greet():
    return "Hello!"

# Pass a function into another function
def call_function(func):
    print(func())

# Call the higher-order function
call_function(greet)
? Creating a Simple Decorator
# Define a simple decorator
def decorator(func):
    def wrapper():
        # Code to run before the original function
        print("Before the function is called.")
        func()
        # Code to run after the original function
        print("After the function is called.")
    return wrapper

# Apply the decorator using @ syntax
@decorator
def say_hello():
    print("Hello, World!")

# Call the decorated function
say_hello()
? Decorator That Accepts Arguments
# Decorator that works with any arguments
def decorator(func):
    def wrapper(*args, **kwargs):
        # Code to run before the original function
        print("Before the function call")
        result = func(*args, **kwargs)
        # Code to run after the original function
        print("After the function call")
        return result
    return wrapper

# Decorate a function that adds two numbers
@decorator
def add(a, b):
    return a + b

# Call the decorated function and print the result
print(add(5, 3))
⚡ Using Multiple Decorators
def uppercase(func):
    def wrapper():
        return func().upper()
    return wrapper

def exclaim(func):
    def wrapper():
        return func() + "!!!"
    return wrapper

@exclaim
@uppercase
def message():
    return "hello"

print(message())  # Output: HELLO!!!

? Output & Explanation

  • Passing functions as arguments: call_function(greet) prints Hello! because greet is passed as a function and then called inside call_function.
  • Simple decorator: say_hello() prints the "before" message, then Hello, World!, then the "after" message. The decorator controls what happens around the original function.
  • Decorator with arguments: add(5, 3) prints the “before/after” messages and returns 8. Using *args and **kwargs keeps the decorator flexible.
  • Multiple decorators: message() first passes through uppercase, becoming "HELLO", then through exclaim, resulting in "HELLO!!!".

? Tips & Best Practices

  • Use the @decorator_name syntax for better readability and cleaner code.
  • Keep decorator logic focused on cross-cutting tasks like logging, authentication, and performance monitoring.
  • Always return the inner wrapper function from your decorator.
  • Use *args and **kwargs in wrappers so your decorator works with any function signature.
  • Be mindful of the order when stacking multiple decorators; the decorator closest to the function runs first.

? Try It Yourself

  • Create a decorator that logs the current time before and after a function call.
  • Write a decorator that only allows a function to run if a certain condition (e.g., a boolean flag) is True.
  • Stack two decorators: one that converts output to uppercase and another that adds a prefix (like "Result: ").
  • Implement a decorator that counts how many times a function has been called and prints the count each time.