Coding problem · Medium · Language Idioms
Decorator with Arguments: @retry
Medium Language practicals Seen at Dropbox, Stripe, Reddit
Implement a decorator factory retry(times, exceptions):
@retry(times=3, exceptions=(TimeoutError,))
def fetch(url): ...
- Call the function up to
timestimes while it raises one ofexceptions; return the first successful result. - After the last failed attempt, re-raise that exception.
- Exceptions not listed propagate immediately.
- The decorated function keeps its
__name__and__doc__, and passes through all arguments.
Hints
Hint 1
Three levels: retry(...) returns decorator(func), which returns wrapper(*args, **kwargs).
Hint 2
functools.wraps(func) copies the metadata.
Write and run your solution
Loading the editor… (JavaScript is required to run code in your browser.)
Solution and explanation
Show the solution
Time O(times) calls · Space O(1)
A decorator with arguments is a function that returns a decorator: retry(times, exceptions) captures the configuration, decorator(func) captures the function, and wrapper(*args, **kwargs) runs the retry loop. except exceptions: accepts a tuple, so only listed types are caught; a bare raise re-raises the current exception with its traceback. functools.wraps keeps __name__, __doc__ and __wrapped__, which matters for logging, debugging and frameworks that inspect functions.
Reference solution
import functools
from typing import Callable
def retry(times: int, exceptions: tuple[type[BaseException], ...]) -> Callable:
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(1, times + 1):
try:
return func(*args, **kwargs)
except exceptions:
if attempt == times:
raise
return wrapper
return decorator
Common mistakes
- Forgetting
functools.wraps(all routes namedwrapper). - Catching
Exceptionregardless of the configured types. raise einstead of bareraisein older code loses context.
Follow-up questions
- Add exponential backoff with an injectable sleep.
- Make it work for
async deffunctions too.