Python 如何重新输入上次引发异常的迭代器/生成器对象
我目前正在尝试使用类似生成器的行为来允许快速上下文切换和并发,而不需要多进程的开销和复杂度,这一点很好,但是我正在尝试找到一种方法,让这些迭代器在引发异常后恢复 据我所知,在引发异常后,发电机在技术上是不可能恢复的,如下所示:Python 如何重新输入上次引发异常的迭代器/生成器对象,python,python-3.x,Python,Python 3.x,我目前正在尝试使用类似生成器的行为来允许快速上下文切换和并发,而不需要多进程的开销和复杂度,这一点很好,但是我正在尝试找到一种方法,让这些迭代器在引发异常后恢复 据我所知,在引发异常后,发电机在技术上是不可能恢复的,如下所示: def test(): yield 1 raise KeyboardInterrupt yield 2 a = test() for x in range(2): try: print('Trying next...')
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)
我知道一般的答案是“不重新编写生成器就无法完成”,但我正在寻找一个pythonichack来让它工作
一种可能的方法是用我自己的状态机替换main,但是这将失去使用迭代器的好处。这个问题假设这不是一个有效的答案
我一直在考虑使用exec,从main读取代码行并一次运行一行代码的可能性,这将允许我在每一行代码上放置try/catch,并允许我在调用异常处理程序后重新尝试该行代码,然而,这将导致一个巨大的性能打击,只是感觉像一个邪恶的事情做
还有什么其他选择?是否存在允许此类功能的调试模块
编辑:
为了澄清这一点,我正在寻找一种不涉及尝试/捕获main中所有内容的方法。如果捕获到异常,则可以在继续之前生成有关异常的信息,但是这不会分配要重新运行的失败行,并且会导致太多的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(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)
我需要解决这个问题好几次,在寻找其他人所做的事情之后,我发现了这个问题 一个选项(可能需要重构一些东西)是简单地创建一个错误处理生成器,并
在生成器中抛出异常(到另一个错误处理生成器),而不是引发它
以下是错误处理生成器函数的外观:
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()
看见第3+部分就是你所做的doing@JochenRitzel谢谢,今晚我将通过该链接阅读。@JochenRitzel尽管它没有回答这个问题,但它是一本非常有用的相关阅读。我目前正在研究代码内省的可能性,并发现:可能会导致一个答案,我会看到。是的,我试图滥用异常机制,这绝对不是生成器/异常的常见情况。您发布的答案将用于发送有关生成器状态的信号,如果您捕获主生成器中的每个异常,则可以使用该答案。我所寻找的是一种不必在main中捕获异常的方法。如果某个_生成器中发生异常(如div by 0),它仍会向上传播,并且不会从它停止的位置继续。这更接近我要查找的内容,但是它需要尝试/保护每一行,但最终需要对预先存在的主生成器功能进行重大更改。我正在试图找到一种从生成器外部捕获异常的方法。呸,那是行不通的。。。你认为你的前面会有一些重构:/你的第三个例子几乎正是我想要的,但是它在捕捉到一个异常后立即引发了StopIteration。我会把结果发表在一篇社论上。我不指望会有一个简单的答案,但投票的答案对尝试很有用。谢谢
handler = err_handler()
for v in my_gen(handler):
v.dance()
v.sing()