Decorators
A decorator takes a function and returns a replacement, which then runs in its place on every call. @name above a def is shorthand for f = name(f).
from functools import wraps
def shout(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
result = fn(*args, **kwargs)
return result.upper()
return wrapper
@shout
def hello(name):
return f"hi {name}"*args, **kwargslets the wrapper accept whatever the original accepts.@wraps(fn)copies the name and docstring, which keeps tracebacks and tools readable.
Decorators with arguments
For @repeat(3), you need one more layer: repeat(3) must return the decorator.
def repeat(n):
def decorator(fn):
@wraps(fn)
def wrapper(*a, **k):
for _ in range(n):
out = fn(*a, **k)
return out
return wrapper
return decoratorYour task
Write a decorator count_calls that stores how many times the function ran in a .calls attribute, starting at 0. Write a decorator factory retry(times) that calls the function up to times times in total, returning the first success and re-raising the last exception if all fail. Both keep the function's name.
The explanation opens here once every check passes.