[英]How to re-enter an iterator/generator object where it last raised an exception
我目前正在尝试使用类似于生成器的行为来允许快速上下文切换和并发,而没有多进程的开销和复杂性,这很好地工作但是我试图找到一种方法让这些迭代器在引发异常后恢复。
据我所知, 技术上不可能在发生异常后恢复生成器,如下所示:
def test():
yield 1
raise KeyboardInterrupt
yield 2
a = test()
for x in range(2):
try:
print('Trying next...')
print(next(a))
except:
pass
结果是:
Trying next...
1
Trying next...
从未达到第二次收益。 这是预期的行为。
我正在寻找的,希望有人有办法,允许运行这样的生成器,在生成器之外处理异常,然后让生成器保留旧的上下文(可能由异常处理修改)代码)然后继续执行。
目前我有以下设置:
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__()
这在使用可能随时失败的网络代码时非常有用,并且添加更多异常处理程序允许运行不同的代码以在重新尝试主代码之前解决某些问题。
理想情况下,我希望能够使用这些异常来解决异常并继续运行main而无需从头开始重新启动main(即,在网络问题的情况下,网络错误处理程序重新连接,继续从在这种情况下我们离开而不是从头开始重新启动main)
我知道一般的答案是“没有重新编写生成器就无法完成”,但我正在寻找一个pythonic hack来实现这一点。
一种可能的方法是用我自己的状态机替换main,但是这会失去使用迭代器的好处。 这个问题假设这不是一个有效的答案。
我一直在研究使用exec的可能性,并实际从main读取代码行并一次运行一行,这样我就可以在每行代码上放置一个try / catch,并允许我重新尝试在调用异常处理程序之后的行,但这会导致巨大的性能损失,并且感觉像是一件邪恶的事情。
还有哪些其他选择? 是否有任何调试模块可以实现这种功能?
编辑:为了澄清,我正在寻找一种方法,不涉及尝试/捕获主要内部的一切。 如果捕获的异常可能会在继续之前产生有关异常的信息,但是这不会分配失败的行重新运行并导致toomany try / excepts。 这里的想法是允许代码具有自己的最小错误处理,以便使用外部错误处理程序。
编辑:Joran提出了几乎可行的以下技术:
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)
这可以工作,但是捕获异常实际上会停止生成器,并且在此生成器上调用next的任何其他尝试都将导致StopIteration,如下所示:
trying next
1
exception yielded
trying next
stop iteration
如果完全正常工作的预期结果将是:
trying next
1
exception yielded
trying next
2
trying next
stop iteration
嗯
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)
但实际上听起来你是在试图滥用异常机制并以不打算使用的方式使用它...
基于你的评论可能是这样的
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)
我需要解决这个问题几次,并在搜索了其他人所做的事后发现了这个问题。
一个选项 - 可能需要稍微重构 - 将简单地创建一个错误处理生成器,并将异常throw
到生成器(到另一个错误处理生成器)而不是raise
它。
以下是错误处理生成器函数的外观:
def err_handler():
# a generator for processing errors
while True:
try:
# errors are thrown to this point in function
yield
except Exception1:
handle_exc1()
except Exception2:
handle_exc2()
except Exception3:
handle_exc3()
except Exception:
raise
像这样使用它:
def my_gen(handler):
while True:
try:
something_tricky()
except Exception as err:
handler.throw(err)
现在你可以安全地做到:
handler = err_handler()
for v in my_gen(handler):
v.dance()
v.sing()
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.