处理上下文管理器中的异常

Mau*_*ldi 16 python exception contextmanager

我有一个代码,我尝试访问资源,但有时它不可用,并导致异常.我尝试使用上下文管理器实现重试引擎,但我无法处理__enter__上下文表单上下文管理器中调用者引发的异常.

class retry(object):
    def __init__(self, retries=0):
        self.retries = retries
        self.attempts = 0
    def __enter__(self):
        for _ in range(self.retries):
            try:
                self.attempts += 1
                return self
            except Exception as e:
                err = e
    def __exit__(self, exc_type, exc_val, traceback):
        print 'Attempts', self.attempts
Run Code Online (Sandbox Code Playgroud)

这是一些只引发异常的例子(我希望处理的那个)

>>> with retry(retries=3):
...     print ok
... 
Attempts 1
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
NameError: name 'ok' is not defined
>>> 
>>> with retry(retries=3):
...     open('/file')
... 
Attempts 1
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
IOError: [Errno 2] No such file or directory: '/file'
Run Code Online (Sandbox Code Playgroud)

有没有办法拦截这个异常并处理这个内部上下文管理器?

the*_*eye 21

引用__exit__,

如果提供了异常,并且该方法希望抑制异常(即,防止它被传播),则它应该返回一个真值.否则,在退出此方法时将正常处理异常.

默认情况下,如果未从函数显式返回值,Python将返回None,这是一个虚假值.在你的情况下,__exit__返回None,这就是允许exeception流过的原因__exit__.

所以,返回一个真实的价值,就像这样

class retry(object):

    def __init__(self, retries=0):
        ...


    def __enter__(self):
        ...

    def __exit__(self, exc_type, exc_val, traceback):
        print 'Attempts', self.attempts
        print exc_type, exc_val
        return True                                   # or any truthy value

with retry(retries=3):
    print ok
Run Code Online (Sandbox Code Playgroud)

输出将是

Attempts 1
<type 'exceptions.NameError'> name 'ok' is not defined
Run Code Online (Sandbox Code Playgroud)

如果您想要重试功能,可以使用生成器实现这一功能,如下所示

def retry(retries=3):
    left = {'retries': retries}

    def decorator(f):
        def inner(*args, **kwargs):
            while left['retries']:
                try:
                    return f(*args, **kwargs)
                except NameError as e:
                    print e
                    left['retries'] -= 1
                    print "Retries Left", left['retries']
            raise Exception("Retried {} times".format(retries))
        return inner
    return decorator


@retry(retries=3)
def func():
    print ok

func()
Run Code Online (Sandbox Code Playgroud)

  • @MauroBaraldi嗯,正如名称所说,上下文管理器只是为了管理特定代码块的上下文.他们无法控制控制流程.因此,装饰者是唯一可行/不那么丑陋的选择. (2认同)

jsb*_*eno 8

要处理__enter__方法中的异常,最简单(并且不太令人惊讶)的事情是将with语句本身包装在try-except子句中,并简单地引发异常 -

但是,with块被定义为不能像这样工作 - 它们本身是"可重复的" - 并且在这里存在一些误解:

def __enter__(self):
    for _ in range(self.retries):
        try:
            self.attempts += 1
            return self
        except Exception as e:
            err = e
Run Code Online (Sandbox Code Playgroud)

一旦你返回self那里,上下文__enter__运行不再存在 - 如果with块内部发生错误,它将自然地流向该__exit__方法.__exit__不,无论如何,该方法不能使执行流程返回到with块的开头.

你可能想要更像这样的东西:

class Retrier(object):

    max_retries = 3

    def __init__(self, ...):
         self.retries = 0
         self.acomplished = False

    def __enter__(self):
         return self

    def __exit__(self, exc, value, traceback):
         if not exc:
             self.acomplished = True
             return True
         self.retries += 1
         if self.retries >= self.max_retries:
             return False
         return True

....

x = Retrier()
while not x.acomplished:
    with x:
        ...
Run Code Online (Sandbox Code Playgroud)


Hen*_*l B 8

我发现contextmanager有用contextlib的,希望这可能有帮助。

from contextlib import contextmanager

@contextmanager
def handler(*args, **kwargs):
  try:
      # print(*args, **kwargs)
      yield
  except Exception:
      # Handle exception     
Run Code Online (Sandbox Code Playgroud)

现在,要使用它,

# Add optional args or kwargs
with handler():
  # Code with probable exception
  print("Hi")
Run Code Online (Sandbox Code Playgroud)


gil*_*gil 7

我认为这很容易,其他人似乎想多了。只需将获取资源的代码放入__enter__,并尝试返回,而不是self,而是获取的资源。在代码中:

def __init__(self, retries):
    ...
    # for demo, let's add a list to store the exceptions caught as well
    self.errors = []

def __enter__(self):
    for _ in range(self.retries):
        try:
            return resource  # replace this with real code
        except Exception as e:
            self.attempts += 1
            self.errors.append(e)

# this needs to return True to suppress propagation, as others have said
def __exit__(self, exc_type, exc_val, traceback):
    print 'Attempts', self.attempts
    for e in self.errors:
        print e  # as demo, print them out for good measure!
    return True
Run Code Online (Sandbox Code Playgroud)

现在试试:

>>> with retry(retries=3) as resource:
...     # if resource is successfully fetched, you can access it as `resource`;
...     # if fetching failed, `resource` will be None
...     print 'I get', resource
I get None
Attempts 3
name 'resource' is not defined
name 'resource' is not defined
name 'resource' is not defined
Run Code Online (Sandbox Code Playgroud)