Unit 3.5 · about 3 h
14 / 324Decorators, wrappers and parameterized decorators
Retry, timing, caching and registration later are all decorators. A wrapper that drops metadata corrupts help, test ids and error messages.
- implement
You can now
- Implement a behavior-preserving wrapper with
functools.wrapsand forwarded*args/**kwargs. - Implement a parameterized decorator factory with its own state, such as
retrywith a budget. - Predict metadata loss, double-application and unhashable-argument failures in wrappers.
Covers
- Decorators, wrappers, metadata and parameterized decorators
Study, in this order
4 steps
- DecoratorsStas' Python Cookbook - selected path
Functions: Decorators (the timed wrapper with
functools.wraps); the no-op fallback pattern for optional dependencies - functools: partial, caching, reduceStas' Python Cookbook - selected path
functools:
partial,lru_cacheand cache bounds,reduce; cache keys need hashable arguments - DecoratorsAlgoMaster Python - structured revision
The Decorators lesson and Exercise: Decorators; write the wrapper from memory first
- functools ModuleAlgoMaster Python - structured revision
The functools Module lesson: compare
wraps,lru_cacheandpartialbehavior after the cookbook pass
Focus on
- A decorator replaces the function object. Without
@wrapsthe name, docstring and signature come from the wrapper; with it,__wrapped__points back. - A decorator with arguments is three levels: the factory call, then the decorator, then the wrapper. State which level each argument belongs to.
lru_cacheand cache key on all arguments. Unhashable arguments raiseTypeError; an unbounded cache grows one entry per distinct call.- Wrappers must forward
*argsand**kwargsuntouched, includingselffor methods; returning the inner result keeps the return contract.
Reproduce closed-book
retry(times) that preserves __name__ and reruns on ValueError, and show three attempts ending in success plus lru_cache raising TypeError on a list argument (both verified on CPython 3.14.4).Traps
- Forgetting
@wrapson a wrapper. Test ids and help show wrapper and failures get harder to trace. - Writing
@retrywithout the factory call. The function lands in thetimesparameter, the name is bound to the inner decorator, and the first call wraps its argument instead of running. - Caching a function of large or unhashable inputs. Memory grows silently or the first call raises TypeError.
Practice
AlgoMaster Python: Exercise: Decorators
Exercises 1 to 5, especially Exercise 2: Preserve Metadata with wraps, Exercise 4: Trace Stacked Decorators and Exercise 5: Retry a Failing Function
Implement the retry factory in your own file first, then add an lru_cache variant of one answer and state its key policy.
Self-test
-
What breaks when
@wrapsis missing from a wrapper?__name__,__doc__and the signature come from the wrapper, and__wrapped__is absent. Debuggers, help and pytest ids show wrapper. -
Why does a cached function fail on
f([1, 2])?Cache keys need hashing andlistis unhashable, so the call raisesTypeErrorbefore the body runs. -
Sketch the three levels of
@retry(3)and what each receives.retry(3)returns the decorator; the decorator receives the function; the wrapper receives the call arguments and loops. -
A timed decorator swallows the return value. Which contract breaks, and what is the one-line fix?
Callers getNoneinstead of the result. Returnfn(*args, **kwargs)from the wrapper, throughtry/finallyfor timing.
Completion criterion
Stop when
Your retry wrapper preserves metadata, retries the seeded failure, and you can name the cache key policy for any wrapper.
Leads to
- 3.3 Attribute lookup, object protocols and dataclasses
@propertyand@classmethoduse decorator syntax over the descriptor protocol. - 5.1 Test levels, oracles, boundary cases and test doubles
unittest.mock.patchworks as a decorator and as a context manager.
Comes after
- 3.2 Argument binding, scope and closuresClosures become wrappers; factories reuse default-argument binding.
Resources for this unit
- Stas' Python Cookbook - selected path Run Part I on dictionaries, iteration, functions, closures, decorators, classes and context managers; Part II on modules, files, paths, environment and arguments; and Part IV on versions, dependencies, packaging and code quality. Finish by building one src-layout package from a blank directory.
- AlgoMaster Python - structured revision Use Functions, Modules and Packages, OOP, Advanced OOP, Exception Handling, Iterators and Generators, Type Hints and Annotations only after the cookbook pass. Open it for recall practice or a diagnosed weak mechanism, not as a second complete course.
Also in this section
- Python language reference Check Data model, Execution model, Import system and Exceptions whenever a claim depends on Python semantics. These pages are the authority when the course and a runtime observation disagree.
- Packaging Python Projects Follow the packaging tutorial once from a blank directory, using a src layout, pyproject.toml, an isolated build and a clean-environment installation test.
- dataclasses - data classes Read the
@dataclassparameters (eq,order,frozen,slots),field()withdefault_factory, frozen instances, post-init processing,replace,asdictandastuple. Use for the validated frozen dataclass in attributes-protocols-dataclasses. - typing - type hints and protocols Read Protocols for structural typing and
runtime_checkablelimits, Generics forTypeVargenerics, andassert_typefor narrowing checks. Use for theProtocoland narrowing parts of type-hints-protocols; PEP 695 syntax itself is in the language reference. - gc - cycle garbage collector Read the cycle collector interface:
collect,get_count,garbageanddisable/enable. Use for the cycle-collection and measurement parts of refcounting-gc-weakrefs. - weakref - weak references Read weak-reference objects (
refcallbacks,proxyobjects) and theWeakValueDictionaryexample. Use for the weak-reference part of refcounting-gc-weakrefs.