Context manager vs decorator
Content managers and function decorators are pretty similar and usually interchangeable. Any context manager can be used as a function decorator as long as it's derived from contextlib.ContextDecorator
. (Mind, that the @contextlib.contextmanager
decorator inherits from that class automatically.)
On the other hand, you can't use any decorator as a context manager due to severe limitation: a context manager always runs a code block exactly once while a decorated function may call original one as many times as it wants (zero included).
PEP 377 proposed the change that allows __enter__
to ask Python not to run a code block at all, but it was rejected.
As a workaround, you should extract a function and apply a decorator to it:
def retry(attempts, exceptions=(Exception,)):
def decorator(func):
def decorated(*args, **kwargs):
for _ in range(attempts - 1):
with suppress(*exceptions):
return func(*args, **kwargs)
return func(*args, **kwargs) # last try
return decorated
return decorator
@retry(10, HTTPError)
def get(url):
requests.get(url).raise_for_status()