Skip to content
Back to skills

Python Decorators

ASecurity

Write decorators that preserve signatures and compose cleanly, including parameterized and class-applied forms. Use when adding cross-cutting behavior to Python functions or fixing decorator-related introspection breakage.

  • 7 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 5, 2026
ai-agentspythongo

Security analysis

A100/100

Scanned September 5, 2026

npx -y skills add Amey-Thakur/AI-SKILLS --skill python-decorators --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Python Decorators?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Python Decorators
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/amey-thakur-python-decorators/badge)](https://www.skillsdirectory.com/skills/amey-thakur-python-decorators)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: python-decorators
description: Write decorators that preserve signatures and compose cleanly, including parameterized and class-applied forms. Use when adding cross-cutting behavior to Python functions or fixing decorator-related introspection breakage.
---

# Python decorators

A decorator is a function transformer. The craft is transforming behavior
without destroying the function's identity: name, docstring, signature,
type hints.

## Method

1. **Always apply functools.wraps.** Without it, `help()`, tracebacks,
   pickling, and any framework that inspects `__name__` or `__doc__` see
   the wrapper, not the function. `@functools.wraps(fn)` on the inner
   wrapper is line one of every decorator.
2. **Keep the wrapper signature generic and forward everything.**
   `def wrapper(*args, **kwargs)` then `return fn(*args, **kwargs)`.
   Dropping the return value is the most common decorator bug; the second
   is swallowing exceptions meant for the caller.
3. **Parameterize with a factory.** `@retry(times=3)` means `retry` is a
   function returning a decorator: three nested defs. If you want both
   `@retry` and `@retry(times=3)` to work, detect the single-callable-arg
   case explicitly; document which form you support.
4. **Preserve types for checkers.** Annotate with ParamSpec:
   `def deco(fn: Callable[P, R]) -> Callable[P, R]`. Without it, every
   decorated function degrades to `Any` and the type checker goes blind
   downstream.
5. **Decide state placement.** Per-call state lives in the wrapper's
   locals; cross-call state (counters, caches) belongs in a closure
   variable or attribute on the wrapper, and needs a lock if threads call
   it. For stdlib cases use `functools.lru_cache` / `cache` instead of
   rolling your own.
6. **Class decorators transform the class, not instances.** They run once
   at definition: registering the class, injecting methods, wrapping
   selected callables from `vars(cls)`. For per-instance behavior use
   `__init_subclass__` or a metaclass only as a last resort.
7. **Support async when callers need it.** A sync wrapper around a
   coroutine function returns an un-awaited coroutine and "works" until
   nothing runs. Branch on `inspect.iscoroutinefunction(fn)` and provide
   an async wrapper path.

## Boundaries

- If the behavior needs configuration at call time rather than definition
  time, a plain higher-order function call is clearer than a decorator.
- Stacked decorators execute bottom-up; ordering-sensitive stacks
  (auth before cache before retry) deserve a comment stating the order.
- Do not use decorators to mutate global registries at import time in
  libraries; imports become side effects the user cannot control.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…