我正在尝试编写一个小型上下文管理器,它将尝试重复执行一些代码,直到代码工作或进行了指定次数的尝试。我曾试图写这篇文章,但在让上下文管理器处理屈服时遇到了困难:
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()
@contextlib.contextmanager
有一个非常明确的契约;它将只恢复一次。它不能用于重新运行代码。
事实上,您根本无法使用上下文管理器来控制重复。这里需要一个循环,而不是上下文管理器。上下文管理器不控制块,只有在进入和退出时才会通知它。
请改用tenacity
包*;它提供了一个装饰器。decorator将函数包装在while True
循环中,该循环将为您重新运行该函数。
您可以将print()
语句移动到一个用@retry
修饰的函数中,然后调用该函数:,从而将其应用于您的案例
import random
from tenacity import retry
@retry
def foo():
print(1 / random.randint(0, 1))
def main():
foo()
*这个答案最初推荐retrying
包,但当该项目处于休眠状态时,它被分叉到了一个带有更新的API的新包中。
你不能那样做。Python中的上下文管理器只是一个协议:
- 调用
__enter__
- 执行一个或多个语句
- 调用
__exit__
第3点。保证会发生,这使得它非常适合处理资源释放等。但这里重要的一点是第2点:上下文管理器将在上下文中运行代码,然后处理3。到那时,被包装的代码已经不见了,被遗忘了,永远无法访问,所以你不能"再次调用它"。contextlib
提供了一个很好的API,可以通过将其作为一个函数来定义上下文管理器:
@contextmanager
def ctxt():
# 1: __enter__ code
yield
# 3: __exit__ code
文件明确规定:
被修饰的函数在被调用时必须返回生成器迭代器。这个迭代器必须只产生一个值,该值将绑定到with语句的as子句中的目标(如果有的话)。
因此,前一点仍然存在。
要重复调用某个东西,所能做的就是将其放入一个函数中,并用"重复直到成功"逻辑装饰该函数:
def dec(f):
def decorated(*args, **kwargs):
while True:
try:
return f(*args, **kwargs)
except:
pass
return decorated
但这与上下文管理器完全无关。
简单的答案是:在Python中,使用上下文管理器无法真正做到这一点。
在上下文管理器中,我们只能使用yield
一次,因此在while
循环中使用yield
是没有意义的。这与Ruby
block
s中使用的yield
不同。
我们也不能访问代码体,例如,我们不能自动获得可以重用的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