我有一个在无人值守的机器上运行的脚本。如果脚本抛出错误,我想记录错误并继续运行,只要它是实用的。这是我的第一个代码草案,但由于下面解释的原因,它不起作用。 (注意:如果在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方式来写这个?
答案 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