【问题标题】:How can a Python context manager try to execute code?Python 上下文管理器如何尝试执行代码?
【发布时间】:2016-08-12 23:02:12
【问题描述】:

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

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 exception try-catch yield contextmanager


    【解决方案1】:

    @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

    【讨论】:

    • retrying 包已经有一段时间没有更新了。但是有一个名为tenacity 的分支正在维护中。
    • @TimB:感谢您的提醒,我已经编辑了答案以推荐坚韧。
    【解决方案2】:

    简短的回答是:你不能用 Python 中的上下文管理器真正做到这一点。

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

    【讨论】:

      【解决方案3】:

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

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

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

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

      而且文档很清楚specifies

      被修饰的函数在调用时必须返回一个生成器迭代器。此迭代器必须只产生一个值,该值将绑定到 with 语句的 as 子句中的目标(如果有)。

      所以前一点仍然存在。

      可以重复调用某事的方法是将其放入一个函数中,并用您的“重复直到成功”逻辑装饰那个函数:

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

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

      【讨论】:

        猜你喜欢
        • 2011-09-07
        • 2020-07-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-12-25
        • 1970-01-01
        相关资源
        最近更新 更多