如何重新输入上次引发异常的迭代器/生成器对象

时间:2014-01-08 20:12:45

标签: python python-3.x

我目前正在尝试使用类似于生成器的行为来允许快速上下文切换和并发,而不需要多进程的开销和复杂性,这很有效但是我试图找到一种方法让这些迭代器在提升后恢复例外。

据我所知,在发生异常后,生成器无法恢复技术

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,并允许我重新编写代码。 - 调用异常处理程序之后的那一行,但是这会导致巨大的性能损失并且感觉像是一件邪恶的事情。

还有哪些其他选择?是否有任何调试模块可以实现这种功能?

编辑: 为了澄清,我正在寻找一种方法,不涉及尝试/捕获main中的所有内容。如果捕获的异常可能会在继续之前产生有关异常的信息,但是这不会分配失败的行重新运行并导致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

2 个答案:

答案 0 :(得分:5)

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)  

答案 1 :(得分:0)

我需要解决这个问题几次,并在搜索了其他人做过的事后发现了这个问题。

一个选项 - 可能需要稍微重构 - 将简单地创建一个错误处理生成器,并throw生成器中的异常(到另一个错误处理生成器)而不是{{1}它。

以下是错误处理生成器函数的外观:

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)