Programming and IT
Python: decorators
A decorator is a function that takes another function and returns a new one with extra behaviour. The `@` sign above a definition is just a shorthand for an assignment. Keep that in mind and the whole topic takes one evening.
In this article
A function is a value, just like a number#
Nothing else works without this: in Python you can put a function in a variable, pass it as an argument and return it from another function.
return
= # no parentheses — the function itself, not its result
# HELLO
# ['A', 'B']
Parentheses mean a call. f = shout is a reference; f = shout("x") is the
result of a call. Mixing the two up causes half the bugs in this topic.
The simplest decorator#
A decorator takes a function, defines a wrapper inside and returns it.
=
return
return
return +
# calling add
# result 5
# 5
Writing @loud above def add is equivalent to the line add = loud(add) after
the definition. There is nothing more to it.
The wrapper takes *args, **kwargs so that it fits a function with any signature,
and it must return the result — otherwise the decorated function starts silently
returning None. That is the bug people spend the longest hunting for.
Keeping the function's name#
Without extra care, the decorated function is replaced by the wrapper, and anything that looks at the name or docstring notices:
# wrapper
The fix is one line — functools.wraps copies the name, docstring and
annotations from the original function to the wrapper:
return
return
"""Add two numbers."""
return +
# add | Add two numbers.
A decorator with arguments#
When a decorator has its own settings, a third level appears: the outer function takes the settings and returns the decorator itself.
=
return
return
return
return
# hello / hello / hello / ok
Hence the rule: a decorator with arguments is written with parentheses —
@repeat(3) — and one without arguments has none — @loud. They are easy to mix
up: @repeat without parentheses passes the function itself into the times
parameter, and the crash happens not at definition time but on the first call.
Order and ready-made decorators#
Several decorators apply from the bottom up — the one closest to def wraps
first:
...
# equivalent to f = a(b(f))
The standard library already has useful decorators. functools.lru_cache
remembers the results of calls:
return
# 832040
# 28
Without the cache, the same fib(30) would make more than a million calls. Other
familiar ones are @property, @staticmethod and @classmethod, which apply to
methods and are covered in the article on Python classes.
Half an hour of practice: write a timed decorator that prints the run time using
time.perf_counter, and a retry(times) decorator that repeats the call when an
exception is raised — the second one relies on
Python exceptions.
Step-by-step plan
- A function in a variableAssign a function to a variable without parentheses, call it by the new name, compare with assigning the result.
- A wrapper by handWrite a wrapper and apply it with the line f = deco(f), without the @ sign.
- Switch to @Replace the manual assignment with the decorator syntax and check that the behaviour is the same.
- Add functools.wrapsCheck __name__ before and after adding wraps.
- A decorator with a parameterWrite repeat(times) and call the function with different parameter values.
Start learning this in your own space
The plan goes into your repository: tick off stages, keep notes — the change history shows how far you have come.
Check yourself
1.The sign @deco above def f() is equivalent to which line?
2.A wrapper calls func(*args) but has no return. What does the decorated function return?
3.How many times is "hello" printed when you call a function decorated with @repeat(times=3)?
4.In "@a / @b / def f()", which decorator wraps the function first — a or b?
Sources
-
The functools modulewraps, lru_cache, cache and other ready-made wrappersfree
-
Language reference: function definitionsThe formal description of what the @ sign expands tofree
-
PEP 318The proposal that introduced decorator syntax and its reasoningfree
Was this helpful?