关于with语句:在python上下文管理器中返回finally块

Return in finally block in python context manager

我最近在python的with语句中遇到了一个奇怪的行为。我有一个代码,它使用Python的上下文管理器回滚__exit__方法中的配置更改。经理在__exit__的finally块中有一个return False值。我在下面的代码中隔离了这种情况-唯一的区别是返回语句的缩进:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Manager1(object):

    def release(self):
        pass # Implementation not important

    def rollback(self):
        # Rollback fails throwing an exception:
        raise Exception("A failure")

    def __enter__(self):
        print"ENTER1"

    def __exit__(self, exc_type, exc_val, exc_tb):
        print"EXIT1"
        try:
            self.rollback()
        finally:
            self.release()
            return False          # The only difference here!


class Manager2(object):

    def release(self):
        pass # Implementation not important

    def rollback(self):
        # Rollback fails throwing an exception:
        raise Exception("A failure")

    def __enter__(self):
        print"ENTER2"

    def __exit__(self, exc_type, exc_val, exc_tb):
        print"EXIT2"
        try:
            self.rollback()
        finally:
            self.release()
        return False      # The only difference here!

在上面的代码中,回滚失败,出现异常。我的问题是,为什么Manager1的行为与Manager2不同。该异常不会在Manager1中的WITH语句之外抛出,也不会在Manager2中退出时抛出。

1
2
3
4
5
6
with Manager1() as m:          
    pass                  # The Exception is NOT thrown on exit here


with Manager2() as m:
    pass                  # The Exception IS thrown on exit here

根据__exit__文件:

If an exception is supplied, and the method wishes to suppress the
exception (i.e., prevent it from being propagated), it should return a
true value. Otherwise, the exception will be processed normally upon
exit from this method.

在我看来,在这两种情况下,出口都没有返回真值,因此在这两种情况下,不应压制例外。但是在Manager1中是这样的。有人能解释一下吗?

我使用的是python 2.7.6。


如果激活了finally子句,这意味着try块已成功完成,或者它引发了已处理的错误,或者try块执行了return

在manager1中,执行return语句作为finally条款的一部分,使其正常终止,返回False。在Manager2类中,finally子句仍然执行,但是如果它是由于引发的异常而执行的,那么它不会阻止该异常向调用链上传播,直到捕获为止(或者直到它用一个traceback终止您的程序)。

只有在不引发异常的情况下,Manager2.__exit__()才会返回false。


我认为理解这一点的一个好方法是看一个独立于所有上下文管理器内容的单独示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> def test ():
        try:
            print('Before raise')
            raise Exception()
            print('After raise')
        finally:
            print('In finally')
        print('Outside of try/finally')

>>> test()
Before raise
In finally
Traceback (most recent call last):
  File"<pyshell#7>", line 1, in <module>
    test()
  File"<pyshell#6>", line 4, in test
    raise Exception()
Exception

因此,您可以看到,当在try块中抛出异常时,执行异常之前的任何代码以及执行finally块中的任何代码。除此之外,其他一切都被跳过。这是因为抛出的异常终止了函数调用。但是,由于异常是在try块中抛出的,因此相应的finally块有最后运行的机会。

现在,如果您注释掉函数中的raise行,您将看到所有代码都被执行,因为函数不会过早结束。