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

How can a Python context manager try to execute code?

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

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()

简短的回答是:在 Python.

中,您不能使用上下文管理器真正做到这一点

我们只能在上下文管理器中 yield 一次,因此在 while 循环中使用 yield 没有意义。这与 Ruby block 中使用的 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

@contextlib.contextmanager有很明确的约定;它只会恢复 一次。它不能用于重新运行代码。

事实上,您根本无法使用上下文管理器来控制重复。您在这里需要一个循环,而不是上下文管理器。上下文管理器不控制块,仅在进入和退出时通知它。

使用 tenacity package* instead; it provides a decorator. The decorator 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 but this was forked into a new package with updated API 该项目处于休眠状态时。

你不能那样做。 Python 中的上下文管理器只是一个协议,它:

  1. 来电__enter__
  2. 执行一个或多个语句
  3. 来电__exit__

第 3 点保证会发生,这使得它非常适合处理资源释放等。但这里的重点是第 2 点:上下文管理器将 运行 代码 上下文中,然后处理 3。到那时,包装的代码已经消失,被遗忘并且永远无法访问,所以你不能 'call it again'。 contextlib 提供了一个很好的 API 来定义你的上下文管理器,只需将它作为一个函数来实现:

@contextmanager
def ctxt():
    # 1: __enter__ code
    yield
    # 3: __exit__ code

并且文档清楚specifies:

The function being decorated must return a generator-iterator when called. This iterator must yield exactly one value, which will be bound to the targets in the with statement’s as clause, if any.

所以前一点仍然存在。

可以做的就是把它放在一个函数中,然后用你的[=44]装饰那个函数=]逻辑:

def dec(f):
    def decorated(*args, **kwargs):
        while True:
            try:
                return f(*args, **kwargs)
            except:
                pass
    return decorated

但这与上下文管理器完全无关。