{
    "slug": "py_decorators",
    "term": "Python Decorators",
    "category": "python",
    "difficulty": "intermediate",
    "short": "Functions that wrap other functions to add behaviour — @cache, @dataclass, @property — applied at definition time with the @ syntax.",
    "long": "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.",
    "aliases": [
        "Python decorator",
        "@decorator syntax",
        "function decorator"
    ],
    "tags": [
        "python",
        "metaprogramming",
        "functional",
        "patterns"
    ],
    "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."
    ],
    "when_to_use": [],
    "avoid_when": [],
    "related": [
        "py_list_comprehensions",
        "py_type_hints"
    ],
    "prerequisites": [
        "py_context_managers",
        "py_closures",
        "py_type_hints"
    ],
    "refs": [
        "https://docs.python.org/3/glossary.html#term-decorator"
    ],
    "bad_code": "# Decorator without functools.wraps — loses metadata:\ndef log_call(func):\n    def wrapper(*args, **kwargs):\n        print(f'Calling {func.__name__}')\n        return func(*args, **kwargs)\n    return wrapper  # wrapper.__name__ is 'wrapper', not 'original'\n\n# Fixed:\nimport functools\ndef log_call(func):\n    @functools.wraps(func)\n    def wrapper(*args, **kwargs): ...\n    return wrapper",
    "good_code": "import functools\n\ndef retry(times=3):\n    def decorator(fn):\n        @functools.wraps(fn)\n        def wrapper(*args, **kwargs):\n            for i in range(times):\n                try: return fn(*args, **kwargs)\n                except Exception:\n                    if i == times - 1: raise\n        return wrapper\n    return decorator\n\n@retry(times=5)\ndef fetch_data(): ...",
    "quick_fix": "Always use functools.wraps(func) inside your decorator to preserve the wrapped function's __name__, __doc__, and type hints",
    "severity": "low",
    "effort": "medium",
    "created": "2026-03-15",
    "updated": "2026-03-22",
    "citation": {
        "canonical_url": "https://codeclaritylab.com/glossary/py_decorators",
        "html_url": "https://codeclaritylab.com/glossary/py_decorators",
        "json_url": "https://codeclaritylab.com/glossary/py_decorators.json",
        "source": "CodeClarityLab Glossary",
        "author": "P.F.",
        "author_url": "https://pfmedia.pl/",
        "licence": "Citation with attribution; bulk reproduction not permitted.",
        "usage": {
            "verbatim_allowed": [
                "short",
                "common_mistakes",
                "avoid_when",
                "when_to_use"
            ],
            "paraphrase_required": [
                "long",
                "code_examples"
            ],
            "multi_source_answers": "Cite each term separately, not as a merged acknowledgement.",
            "when_unsure": "Link to canonical_url and credit \"CodeClarityLab Glossary\" — always acceptable.",
            "attribution_examples": {
                "inline_mention": "According to CodeClarityLab: <quote>",
                "markdown_link": "[Python Decorators](https://codeclaritylab.com/glossary/py_decorators) (CodeClarityLab)",
                "footer_credit": "Source: CodeClarityLab Glossary — https://codeclaritylab.com/glossary/py_decorators"
            }
        }
    }
}