I have some code where I try to reach a resource but sometimes it is unavailable, and results in an exception. I tried to implement a retry engine using context managers, but I can't handle the exception raised by the caller inside the __enter__
context for my context manager.
class retry(object): def __init__(self, retries=0): self.retries = retries self.attempts = 0 def __enter__(self): for _ in range(self.retries): try: self.attempts += 1 return self except Exception as e: err = e def __exit__(self, exc_type, exc_val, traceback): print 'Attempts', self.attempts
These are some examples which just raise an exception (which I expected to handle)
>>> with retry(retries=3): ... print ok ... Attempts 1 Traceback (most recent call last): File "<stdin>", line 2, in <module> NameError: name 'ok' is not defined >>> >>> with retry(retries=3): ... open('/file') ... Attempts 1 Traceback (most recent call last): File "<stdin>", line 2, in <module> IOError: [Errno 2] No such file or directory: '/file'
Is there any way to intercept these exception(s) and handle them inside the context manager?
Context managers allow you to allocate and release resources precisely when you want to. The most widely used example of context managers is the with statement. Suppose you have two related operations which you'd like to execute as a pair, with a block of code in between.
Yes, the context manager will be available outside the with statement and that is not implementation or version dependent. with statements do not create a new execution scope.
__enter__ and [__exit__] both are methods that are invoked on entry to and exit from the body of "the with statement" (PEP 343) and implementation of both is called context manager. the with statement is intend to hiding flow control of try finally clause and make the code inscrutable.
The with statement in Python is a quite useful tool for properly managing external resources in your programs. It allows you to take advantage of existing context managers to automatically handle the setup and teardown phases whenever you're dealing with external resources or with operations that require those phases.
Quoting __exit__
,
If an exception is supplied, and the method wishes to suppress the exception (i.e., prevent it from being propagated), it should return a true value. Otherwise, the exception will be processed normally upon exit from this method.
By default, if you don't return a value explicitly from a function, Python will return None
, which is a falsy value. In your case, __exit__
returns None
and that is why the exeception is allowed to flow past the __exit__
.
So, return a truthy value, like this
class retry(object): def __init__(self, retries=0): ... def __enter__(self): ... def __exit__(self, exc_type, exc_val, traceback): print 'Attempts', self.attempts print exc_type, exc_val return True # or any truthy value with retry(retries=3): print ok
the output will be
Attempts 1 <type 'exceptions.NameError'> name 'ok' is not defined
If you want to have the retry functionality, you can implement that with a decorator, like this
def retry(retries=3): left = {'retries': retries} def decorator(f): def inner(*args, **kwargs): while left['retries']: try: return f(*args, **kwargs) except NameError as e: print e left['retries'] -= 1 print "Retries Left", left['retries'] raise Exception("Retried {} times".format(retries)) return inner return decorator @retry(retries=3) def func(): print ok func()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With