pythonic方式尝试(反复)捕获异常

时间:2014-09-16 16:48:24

标签: python exception closures

我有一个在无人值守的机器上运行的脚本。如果脚本抛出错误,我想记录错误并继续运行,只要它是实用的。这是我的第一个代码草案,但由于下面解释的原因,它不起作用。 (注意:如果在T秒内捕获到N个错误,则更真实的concede函数将返回true。或者其他内容。)

from contextlib import contextmanager

@contextmanager
def perseverance(concede = lambda: False):
    while (True):
        try:
            yield
        except Exception, e:
            if (concede()):
                log_exception(e, "conceding")
                raise
            else:
                log_exception(e, "retrying")

这将允许我做类似的事情:

def quit_after(n):
    n = [n]         # make n mutable for the closure :P
    def quitter():
        if (n[0] <= 0): return True
        n[0] -= 1
        return False
    return quitter

with perseverance(quit_after(3)):
    do_complex_script()

这不起作用,因为@contextmanager对异常处理很挑剔。此代码将失败:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File ".../python2.7/contextlib.py", line 36, in __exit__
    raise RuntimeError("generator didn't stop after throw()")
RuntimeError: generator didn't stop after throw()

我可以简单地重写perseverance(),不使用上下文管理器,但没有优雅的语法,如下所示:

def with_perseverance(fn, concede = lambda: False):
    while (True):
        try:
            fn()
        except Exception, e:
            if (concede()):
                log_exception(e, "conceding")
                raise
            else:
                log_exception(e, "retrying")

并将其称为:

with_perseverance(do_complex_script, quit_after(3))

问题

有可能因为过于主观而被选中投票:是否有更好/更多的pythonic方式来写这个?

1 个答案:

答案 0 :(得分:1)

contextmanager对于异常处理并不挑剔;它只是希望你只有yield一次。上下文管理器不支持重新进入。

如果你想要,你有几个选择。一种是使用with - for组合:

from contextlib import contextmanager

class MutableValue:
    def __init__(self, value):
        self.value = value

@contextmanager
def null_context():
    yield

@contextmanager
def catch_and_log(mutable_return_successful):
    try:
        yield
    except Exception as e:
        print("ERROR:", e)
    else:
        mutable_return_successful.value = True

def quit_after(n):
    for _ in range(n-1):
        successful = MutableValue(False)
        yield catch_and_log(successful)
        if successful.value:
            return

    yield null_context()

for ctx in quit_after(5):
    with ctx:
        1/0

#>>> ERROR: division by zero
#>>> ERROR: division by zero
#>>> ERROR: division by zero
#>>> ERROR: division by zero
#>>> Traceback (most recent call last):
#>>>   File "", line 31, in <module>
#>>> ZeroDivisionError: division by zero

因为with需要与for进行通信,所以我必须做一些hackery来获得正确的回报。它比基于类的解决方案更具可读性。

另一个更容易的选择是滥用装饰器:

def quit_after(n):
    def inner(f):
        for _ in range(n-1):
            try:
                f()
            except Exception as e:
                print("ERROR:", e)
            else:
                return
        f()

    return inner

@quit_after(5)
def _():
    1/0
#>>> ERROR: division by zero
#>>> ERROR: division by zero
#>>> ERROR: division by zero
#>>> ERROR: division by zero
#>>> Traceback (most recent call last):
#>>>   File "", line 44, in <module>
#>>>   File "", line 40, in inner
#>>>   File "", line 46, in _
#>>> ZeroDivisionError: division by zero