Python上下文管理器如何尝试执行代码



我正在尝试编写一个小型上下文管理器,它将尝试重复执行一些代码,直到代码工作或进行了指定次数的尝试。我曾试图写这篇文章,但在让上下文管理器处理屈服时遇到了困难:

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中的上下文管理器只是一个协议:

  1. 调用__enter__
  2. 执行一个或多个语句
  3. 调用__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

最新更新