首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >处理上下文管理器中的异常

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

Stack Overflow用户
提问于 2016-02-18 13:54:26
回答 5查看 42.1K关注 0票数 36

我有一些代码试图访问资源,但有时它是不可用的,并导致异常。我试图使用上下文管理器实现重试引擎,但是我无法处理上下文管理器的__enter__上下文中的调用方引发的异常。

代码语言:javascript
复制
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

以下是一些只会引发异常的例子(我希望能处理这些异常)

代码语言:javascript
复制
>>> 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'

有没有办法拦截这些异常并在上下文管理器中处理它们?

EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2016-02-18 13:58:09

引用__exit__的话,

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

默认情况下,如果不显式地从函数返回值,Python将返回None,这是一个falsy值。在您的示例中,__exit__返回None,这就是为什么允许exeception通过__exit__

所以,返回一个真实的值,如下所示

代码语言:javascript
复制
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

输出将是

代码语言:javascript
复制
Attempts 1
<type 'exceptions.NameError'> name 'ok' is not defined

如果您想拥有重试功能,可以使用装饰器实现它,如下所示

代码语言:javascript
复制
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()
票数 43
EN

Stack Overflow用户

发布于 2016-02-18 13:57:41

要处理__enter__方法中的异常,最简单(也不那么令人惊讶)的事情是将with语句本身包装在一个try-除了子句中,然后简单地引发异常-

但是,with块的设计显然不能像这样工作--仅凭自己的“可检索性”--这里存在一些误解:

代码语言:javascript
复制
def __enter__(self):
    for _ in range(self.retries):
        try:
            self.attempts += 1
            return self
        except Exception as e:
            err = e

返回self后,__enter__运行的上下文就不再存在--如果在with块中发生错误,它将自然地流向__exit__方法。不,__exit__方法无论如何不能使执行流返回到with块的开头。

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

代码语言:javascript
复制
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:
        ...
票数 15
EN

Stack Overflow用户

发布于 2016-02-18 14:24:25

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

代码语言:javascript
复制
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

现在试一试:

代码语言:javascript
复制
>>> 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
票数 8
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/35483359

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档