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()