← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Python Decorators

Python Python 2.4+ Intermediate
debt(d5/e1/b3/t5)
d5 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'specialist tool catches' (d5), pylint/ruff have rules (e.g. ruff's B902-related, pylint's missing functools.wraps detection) that flag decorators without @functools.wraps, but subtler issues like argument handling or stateful decorators remain silent until runtime.

e1 Effort Remediation debt — work required to fix once spotted

Closest to 'one-line patch' (e1), the quick_fix is literally adding @functools.wraps(func) above the wrapper — a single-line addition per decorator.

b3 Burden Structural debt — long-term weight of choosing wrong

Closest to 'localised tax' (b3), decorators are typically scoped to specific cross-cutting concerns (auth, logging, caching) and don't define the system's shape, though heavy decorator stacking in one component adds a maintenance tax there.

t5 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'notable trap' (t5), the misconception that decorators modify functions in place rather than replacing them is a documented gotcha most Python devs eventually learn; losing __name__/__doc__ silently is the canonical surprise.

About DEBT scoring →

Also Known As

Python decorator @decorator syntax function decorator

TL;DR

Functions that wrap other functions to add behaviour — @cache, @dataclass, @property — applied at definition time with the @ syntax.

Explanation

A decorator is a callable that takes a function and returns a modified version. @functools.wraps preserves the original function's name and docstring. Built-in decorators: @property (computed attribute), @staticmethod (no self/cls), @classmethod (receives cls), @functools.cache (memoisation), @functools.lru_cache(maxsize=128). Decorator factories take arguments: @lru_cache(maxsize=256). Class decorators: @dataclass generates __init__, __repr__, __eq__ from class annotations — Python's equivalent of PHP 8.1 readonly classes. Stacking: @app.route('/') @login_required applies inside-out. PHP's equivalent is attribute-based annotations (#[Route]) or middleware patterns — Python decorators are more powerful because they execute arbitrary code, not just metadata.

Diagram

flowchart LR
    subgraph Decorator_Mechanics
        ORIG[def my_func]
        WRAP[def decorator wraps func]
        APPLIED[my_func = decorator my_func]
        ORIG --> WRAP --> APPLIED
    end
    subgraph Common_Uses
        TIMING[measure_time decorator<br/>log execution duration]
        CACHE2[functools.lru_cache<br/>memoize results]
        AUTH2[require_auth decorator<br/>check before running]
        RETRY2[retry decorator<br/>retry on exception]
    end
    subgraph Syntax
        AT[at symbol decorator<br/>above function definition<br/>syntactic sugar]
    end
style APPLIED fill:#238636,color:#fff
style CACHE2 fill:#1f6feb,color:#fff
style AUTH2 fill:#f85149,color:#fff

Common Misconception

Python decorators modify the original function in place. Decorators replace the original function with a wrapper — without functools.wraps, the decorated function loses its __name__, __doc__, and signature. Always use @functools.wraps(func) inside decorators to preserve metadata.

Why It Matters

Python decorators wrap functions or classes to add behaviour — used for caching, logging, auth, retry, and timing without modifying the function's core logic.

Common Mistakes

  • Not using functools.wraps — the decorated function loses its __name__ and __doc__.
  • Decorators that do not handle the wrapped function's arguments correctly — use *args, **kwargs.
  • Stateful decorators without thread safety — mutable state in a decorator is shared across calls.
  • Stacking too many decorators — execution order is bottom-up for application and top-down for stacking.

Code Examples

✗ Vulnerable
# Decorator without functools.wraps — loses metadata:
def log_call(func):
    def wrapper(*args, **kwargs):
        print(f'Calling {func.__name__}')
        return func(*args, **kwargs)
    return wrapper  # wrapper.__name__ is 'wrapper', not 'original'

# Fixed:
import functools
def log_call(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs): ...
    return wrapper
✓ Fixed
import functools

def retry(times=3):
    def decorator(fn):
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            for i in range(times):
                try: return fn(*args, **kwargs)
                except Exception:
                    if i == times - 1: raise
        return wrapper
    return decorator

@retry(times=5)
def fetch_data(): ...

Added 15 Mar 2026
Edited 22 Mar 2026
Views 60
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 1 ping W 1 ping T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 2 pings T 0 pings F 2 pings S 1 ping S 3 pings M 0 pings T 0 pings W 0 pings T 1 ping F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 2 pings S 0 pings M 2 pings T 0 pings W
No pings yet today
PetalBot 1 Bing 1
Perplexity 7 Amazonbot 7 Scrapy 6 Ahrefs 4 Google 3 Bing 3 ChatGPT 3 SEMrush 3 Unknown AI 2 Claude 2 PetalBot 2 Meta AI 1
crawler 39 crawler_json 4
DEV INTEL Tools & Severity
🟢 Low ⚙ Fix effort: Medium
⚡ Quick Fix
Always use functools.wraps(func) inside your decorator to preserve the wrapped function's __name__, __doc__, and type hints
📦 Applies To
python 2.4 web cli
🔗 Prerequisites
🔍 Detection Hints
Decorator missing @functools.wraps(func) losing wrapped function metadata; repeated cross-cutting concerns not extracted as decorators
Auto-detectable: ✓ Yes pylint ruff mypy
⚠ Related Problems
🤖 AI Agent
Confidence: Low False Positives: High ✗ Manual fix Fix: Medium Context: Function Tests: Update


✓ schema.org compliant