CodeNebula
Python🔧Core Languagebeginner

Decorators

Python · Core Language · Run with python3

Explanation

A decorator is a callable that takes a function and returns a (usually modified) function. The `@decorator` syntax is syntactic sugar for `func = decorator(func)`. Decorators are used for cross-cutting concerns: logging, timing, access control, caching, retry logic, and input validation — without modifying the decorated function's body. `functools.wraps` preserves the wrapped function's metadata (`__name__`, `__doc__`).

Real-World Scenario

A web framework registers URL routes with `@app.route("/users")`. An internal API enforces authentication with `@require_auth`. A data pipeline retries flaky network calls with `@retry(max_attempts=3)`. All these are decorators — the business logic is untouched, and behaviour is composed. ```python import functools import time def timer(func): @functools.wraps(func) def wrapper(*args, **kwargs): start = time.perf_counter() result = func(*args, **kwargs) elapsed = time.perf_counter() - start print(f"{func.__name__} took {elapsed:.4f}s") return result return wrapper def retry(max_attempts=3): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): for attempt in range(1, max_attempts + 1): try: return func(*args, **kwargs) except Exception as e: if attempt == max_attempts: raise print(f"Attempt {attempt} failed: {e}. Retrying...") return wrapper return decorator @timer @retry(max_attempts=3) def fetch_data(url: str) -> str: return f"data from {url}" print(fetch_data("https://example.com")) ```

Quick Snippet

snippet.py
import functools
import time

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{func.__name__} took {elapsed:.4f}s")
        return result
    return wrapper

def retry(max_attempts=3):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts:
                        raise
                    print(f"Attempt {attempt} failed: {e}. Retrying...")
        return wrapper
    return decorator

@timer
@retry(max_attempts=3)
def fetch_data(url: str) -> str:
    return f"data from {url}"

print(fetch_data("https://example.com"))

Full Example

decorators.py
# Compile: python3 decorators.py
# Output:
# fetch_data took

import functools
import time

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{func.__name__} took {elapsed:.4f}s")
        return result
    return wrapper

def retry(max_attempts=3):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts:
                        raise
                    print(f"Attempt {attempt} failed: {e}. Retrying...")
        return wrapper
    return decorator

@timer
@retry(max_attempts=3)
def fetch_data(url: str) -> str:
    return f"data from {url}"

result = fetch_data("https://example.com")
print(result[:20])

# Verify @functools.wraps preserves metadata
print(fetch_data.__name__)   # fetch_data (not 'wrapper')
Run command
python3 decorators.py