在python上下文管理器中返回finally块

时间:2014-07-03 11:19:08

标签: python with-statement contextmanager

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

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退出时抛出异常。

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

根据documentation of __exit__

  

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

在我看来,在这两种情况下,退出都不会返回True,因此在这两种情况下都不应该禁止例外。但是在Manager1中它是。任何人都可以解释一下吗?

我使用的是Python 2.7.6。

2 个答案:

答案 0 :(得分:3)

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

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

如果没有引发异常,

False将仅返回False。

答案 1 :(得分:1)

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

>>> 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行,您将看到所有代码都已执行,因为该函数不会过早结束。