I am currently attempting to use generator-like behaviour to allow quick context switches and concurrency without the overhead and complicatedness of multi-processes and this is working quite well however I am trying to find a way for these iterators to resume after raising an exception.
To the best of my knowledge, it is technically impossible for a generator to resume after raising an exception as seen:
def test():
yield 1
raise KeyboardInterrupt
yield 2
a = test()
for x in range(2):
try:
print('Trying next...')
print(next(a))
except:
pass
results in:
Trying next...
1
Trying next...
with the second yield never being reached. This is expected behaviour.
What I am looking for, and hopefully someone has a way to, would be to allow such a generator to be run, have the exception handled outside of the generator, and then have the generator keep the old context(possibly modified by the exception handling code) and then continue execution.
currently I have the following setup:
class base:
def __iter__(self):
self._run = self.main()
return self
def __next__(self,_run=None):
try:
return next(self._run)
except tuple(self.exceptions.keys()) as e: # if exception handler exists, use it
exception = e.__class__
self._run = self._end() # end thread if the handler also fails
handler = self.__next__(self.exceptions[exception])
if handler: # if handler returns something use it, else it should replace _run manually, otherwise thread will end
self._run = handler
except (StopIteration,exceptions.u_end):
#Execution done
raise
except:
#unhandled excpetion, should log
raise
def _end(self):
raise exceptions.u_end
yield
def main(self):
"""Replace me with something useful"""
self.exceptions = {exceptions.u_reset:self._reset} #set handler
for x in range(2):
print("Main:",x)
yield x
raise exceptions.u_reset
print('This should NEVER be run!!!!')
def _reset(self):
self.__iter__()
This works great when working with networking code which may fail at any time and adding more exception handlers allows different code to be run to resolve certain issues before the main code is re-tried.
Ideally, I would like to be able to use these exceptions to resolve the exception and continue running main without having to restart main from the beginning (ie, in case of network issue, the network error handler reconnects, it would be nice to continue from where we left off instead of restarting main from the beginning in this case)
I know that the general answer is "it can't be done without re-writing the generator" but I am looking for a pythonic hack to get this to work.
One possible way is to replace main with a state machine of my own, however this would lose the benefits of using an iterator. This question assumes that this is not a valid answer.
I have been looking at the possibility of using exec and actually reading the lines of code from main and running them one at a time, this would allow me to place a try/catch on every line of code and allow me to re-try that line after the exception handler has been called, however this would result in a huge performance hit and just feels like an evil thing to do.
What other options are there? Are there any debug modules that would allow this kind of functionality?
Edit: To clarify, I am looking for a method that does not involve try/catching everything inside of main. If the exception where caught it would be possible to yield information about the exception before continuing however this would not allot the failed line to be re-run and would result in toomany try/excepts. The idea here is to allow the code with minimal error handling of its own in order to make use of external error handlers.
Edit: Joran suggested the following technique which almost works:
def safe_test(t):
while True:
try:
yield next(t)
except StopIteration:
print('stop iteration')
break
except BaseException as e:
print('exception yielded')
yield e
def test():
yield 1
raise KeyboardInterrupt
yield 2
for x in safe_test(test()):
print('trying next')
print(x)
This would work however catching the exception actually stops the generator and any other attempts at calling next on this generator will result in StopIteration as seen here:
trying next
1
exception yielded
trying next
stop iteration
The expected result if this where fully working would have been:
trying next
1
exception yielded
trying next
2
trying next
stop iteration
ummm
def some_generator():
for i in range(100):
if i % 25 == 0:
yield Exception("whatever")
yield i
for val in some_generator():
if isinstance(val,Exception):
print ("some Error happened....",val)
else:
print(val)
but really it sounds like you are trying to abuse the Exception mechanism and use it in ways it was not intended to be used ...
based on your comment maybe something like this
def some_generator(some_function):
for i in range(100):
try:
yield some_function(i)
except Exception as e:
yield e
def a_fn(x):
return x/(x%25)
for val in some_generator(a_fn):
if isinstance(val,Exception):
print ("some Error happened....",val)
else:
print(val)
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