How can a Python context manager try to execute code?

こ雲淡風輕ζ 提交于 2019-12-24 17:09:13

问题


I'm trying to write a small context manager that'll try to execute some code repeatedly until the code works or until a specified number of tries has been made. I have attempted to write this but am encountering a difficulty with having the context manager handle problems when yielding:

Exception RuntimeError: 'generator ignored GeneratorExit'

How should I code this?

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

回答1:


@contextlib.contextmanager has a very clear contract; it'll only be resumed once. It can't be used to re-run code.

In fact, you can't use a context manager to control repetitions at all. You need a loop here, not a context manager. A context manager doesn't control the block, it is only informed when entering and exiting.

Use the tenacity package* instead; it provides a decorator. The decorator wraps a function in a while True loop that'll re-run the function for you.

You'd apply it to your case by moving the print() statement into a function, decorated with @retry, then calling that function:

import random
from tenacity import retry

@retry
def foo():
    print(1 / random.randint(0, 1))

def main():
    foo()

* This answer originally recommended the retrying package but this was forked into a new package with updated API when that project fell dormant.




回答2:


You can't do that. A context manager in Python is simply a protocol which:

  1. Calls __enter__
  2. Executes one or more statements
  3. Calls __exit__

Point 3. is guaranteed to happen, which makes it great for handling resource releasing, etc. But the important point here is point 2.: the context manager will run the code within the context, then process 3. By that point, the wrapped code is gone, forgotten and unreachable forever, so you can't 'call it again'. contextlib offers a nice API for defining your context manager simply by doing it as a function:

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

And the documentation clearly 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.

So the previous point remains.

What you can do to call something repeteadly is to put it in a function, and decorate that function with your 'repeat until success' logic:

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

But this is totally unrelated to context managers.




回答3:


The short answer is: you can't really do this with context manager in Python.

We can only yield once in a context manager, so it doesn't make sense to use yield inside a while loop. This is different from the yield used in Ruby blocks.

And we don't have access to the body of code either, for example, we don't automatically get something like a function that we can reuse.

So, no, if you do want to implement a reusable retry logic, use a function instead.

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


来源:https://stackoverflow.com/questions/36740887/how-can-a-python-context-manager-try-to-execute-code

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!