Python Decorators Explained: Logging, Timing and Reusable Function Wrappers

Python course module guide: Functions and reusable design

Python decorators wrap a function or class with reusable behavior. They are common in web routes, authorization, caching, logging and testing, but they should make intent clearer rather than hide important control flow.

What you will learn

  • Understand functions as first-class objects.
  • Build a wrapper and preserve metadata.
  • Create timing and configurable decorators.
  • Recognize decorator-order and state mistakes.

What a decorator changes

A decorator receives a callable and returns a callable. The @name

def announce(function):
    def wrapper():
        print("Starting")
        result = function()
        print("Finished")
        return result
    return wrapper

@announce
def build_report():
    return "report ready"

The decorated name now refers to wrapper. Review Python functions, parameters and scope before moving to configurable decorators.

Accept any function signature

from functools import wraps

def audit_call(function):
    @wraps(function)
    def wrapper(*args, **kwargs):
        print(f"Calling {function.__name__}")
        return function(*args, **kwargs)
    return wrapper

*args and **kwargs forward positional and keyword arguments. functools.wraps preserves the original name, documentation and other metadata used by help tools, test reports and frameworks.

Build a timing decorator

from functools import wraps
from time import perf_counter

def timed(function):
    @wraps(function)
    def wrapper(*args, **kwargs):
        started = perf_counter()
        try:
            return function(*args, **kwargs)
        finally:
            elapsed = perf_counter() - started
            print(f"{function.__name__}: {elapsed:.4f}s")
    return wrapper

The finally block records time even when the wrapped function raises an exception. In production, use the logging module instead of uncontrolled prints. The Python logging project demonstrates levels, structured context and rotating files.

Decorator factories with configuration

from functools import wraps

def require_role(allowed_role):
    def decorator(function):
        @wraps(function)
        def wrapper(user, *args, **kwargs):
            if user.get("role") != allowed_role:
                raise PermissionError("Access denied")
            return function(user, *args, **kwargs)
        return wrapper
    return decorator

@require_role("admin")
def export_users(user):
    return "export started"

The outer function captures configuration, the middle function accepts the target and the wrapper runs on each call. Real authorization needs a trusted identity, explicit policies and safe error responses; the example teaches structure, not a complete security system.

Stacking decorators

@audit_call
@timed
def calculate_summary(records):
    return sum(records)

The decorator closest to the function is applied first, so this is equivalent to calculate_summary = audit_call(timed(calculate_summary)). Order matters when one wrapper changes arguments, catches errors, caches results or checks permissions.

Decorators and generators

A normal timing wrapper measures creation of a generator object, not the full iteration. If you decorate a generator function, decide whether you need to measure each yielded item or the complete consumption period. Learn the execution model in the Python iterators and generators guide before wrapping lazy pipelines.

How to test decorated behavior

Test both the added behavior and the original return value. Avoid tests that depend only on printed text. Inject a logger, clock or policy function when practical so tests can substitute predictable dependencies. The pytest unit testing guide shows fixtures, parametrization and monkeypatching.

Common decorator mistakes

  • Forgetting to return the wrapped function’s result.
  • Omitting functools.wraps and losing useful metadata.
  • Accepting no arguments when the target has parameters.
  • Sharing mutable state accidentally across every decorated call.
  • Catching broad exceptions and hiding failures.
  • Stacking many wrappers until execution order becomes difficult to trace.

When a decorator is appropriate

Use a decorator when the same surrounding behavior genuinely belongs to several callables: tracing, timing, authorization checks or retry policy. Use a normal function call when the action is part of the business workflow and should be visible in the function body. Explicit code is often easier to debug than a clever abstraction.

Practice exercise

Create a configurable decorator that records a function name, duration and success status. Preserve metadata, forward all arguments, return the original value and re-raise failures after logging them. Test a successful function and a function that raises an error.

Python decorator FAQs

Is @decorator special syntax?

It is convenient syntax for applying a callable transformation when a function or class is defined. The decorated name receives the returned object.

Can a decorator accept arguments?

Yes. A decorator factory accepts configuration and returns the actual decorator. This adds one nesting level, so clear names and tests become important.

Why does my function name become wrapper?

The wrapper replaced the original callable without copying its metadata. Apply functools.wraps to the wrapper to preserve the original identity for documentation and tools.

Can decorators be removed at runtime?

Decoration normally replaces the name during definition, so it is not designed as a temporary toggle. For optional behavior, inject a policy or keep the undecorated function accessible deliberately. The __wrapped__ attribute added by functools.wraps can help tooling and focused tests, but application code should not depend on bypassing important authorization or validation wrappers.

Official reference: Python glossary definition of decorator.

Learn Python with guided practice in Visakhapatnam

These concepts become useful when you apply them in exercises, assignments and reviewed projects. Explore Python training in Vizag for the complete curriculum, classroom and online learning options, and current batch details.

Python module learning path

Continue through the related practical guides in this course-module series:

  1. Python match-case pattern matching
  2. Python list comprehensions
  3. Python iterators and generators
  4. Python decorators
  5. Python context managers
  6. Python custom exceptions
  7. pytest unit testing for beginners
  8. Python API pagination and retries
  9. pandas GroupBy and merge
  10. Flask REST API with SQLite

Leave a Comment

Your email address will not be published. Required fields are marked *