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

时间:2016-04-20 10:24:30

标签: python exception try-catch yield contextmanager

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

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

3 个答案:

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

  1. 致电__enter__
  2. 执行一个或多个陈述
  3. 致电__exit__
  4. 确保点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