我试图编写一个小的上下文管理器,它会尝试重复执行某些代码,直到代码工作或者直到指定的尝试次数为止。我试图写这个但是遇到困难时让上下文管理器在屈服时处理问题:
Exception RuntimeError: 'generator ignored GeneratorExit'
我该如何编码?
import contextlib
import random
def main():
with nolube():
print(1 / random.randint(0, 1))
@contextlib.contextmanager
def nolube(
tries = None # None: try indefinitely
):
"""
Create a context for trying something repeatedly.
"""
tries_done = 0
rekt = True
if tries is None:
while rekt is True:
try:
yield
rekt = False
except:
tries_done += 1
pass
else:
while rekt is True and tries_done <= tries:
try:
yield
rekt = False
except:
tries_done += 1
pass
if __name__ == "__main__":
main()
答案 0 :(得分:3)
@contextlib.contextmanager
签订了非常明确的合同;它只能恢复一次。它无法用于重新运行代码。
事实上,您无法使用上下文管理器来控制所有的重复。你需要一个循环,而不是上下文管理器。上下文管理器不控制块,只有在进入和退出时才会被通知。
使用tenacity
package * 代替;它提供了装饰。装饰者wraps a function in a while True
loop将为您重新运行该功能。
您可以将print()
语句移动到用@retry
修饰的函数中,然后调用该函数,将其应用于您的案例:
import random
from tenacity import retry
@retry
def foo():
print(1 / random.randint(0, 1))
def main():
foo()
* 这个答案最初推荐retrying
package,但当该项目处于休眠状态时,这是forked into a new package with updated API。
答案 1 :(得分:2)
你做不到。 Python中的上下文管理器只是一个协议:
__enter__
__exit__
确保点3发生,这使得它非常适合处理资源释放等。但重点在于第2点:上下文管理器将在上下文中运行代码,然后处理3.到那时,包装的代码永远消失,遗忘和无法访问,因此你不能再“再次调用它”。 contextlib
提供了一个很好的API,可以通过将其作为函数来定义您的上下文管理器:
@contextmanager
def ctxt():
# 1: __enter__ code
yield
# 3: __exit__ code
文档明确specifies:
正在修饰的函数在调用时必须返回generator-iterator。此迭代器必须只生成一个值,该值将绑定到with语句的as子句中的目标(如果有)。
所以前一点仍然存在。
你可以做什么来重复调用某些东西是将它放在一个函数中,并用你的'重复直到成功'逻辑来装饰 函数:
def dec(f):
def decorated(*args, **kwargs):
while True:
try:
return f(*args, **kwargs)
except:
pass
return decorated
但这与上下文管理者完全无关。
答案 2 :(得分:1)
简短的回答是:你不能用Python中的上下文管理器真正做到这一点。
我们在上下文管理器中只能yield
一次,因此在yield
循环中使用while
没有意义。这与yield
Ruby
中使用的block
不同。
我们也无法访问代码体,例如,我们不会自动获得可以重用的function
之类的内容。
所以,不,如果你想要实现可重用的retry
逻辑,请改用函数。
def retry(func, n_times=None):
i = 1
while True:
try:
return func()
except Exception:
i += 1
if n_times and i >= n_times:
raise