为什么当__enter__引发异常时不执行__exit__

时间:2019-08-16 12:19:03

标签: python contextmanager

最近我想知道当__exit__引发异常时不隐式调用__enter__的背后原因是什么? 为什么这样设计?我正在实施服务运行程序类,以便可以通过'with'关键字使用,事实证明从未调用过__exit__

示例:

class ServiceRunner(object):
    def __init__(self, allocation_success):
        self.allocation_success = allocation_success

    def _allocate_resource(self):
        print("Service1 running...")
        print("Service2 running...")

        # running service3 fails ...
        if not self.allocation_success:
            raise RuntimeError("Service3 failed!")
        print("Service3 running...")

    def _free_resource(self):
        print("All services freed.")

    def __enter__(self):
        self._allocate_resource()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self._free_resource()

用法:

with ServiceRunner(allocation_success=True):
    pass

try:
    with ServiceRunner(allocation_success=False):
        pass
    except Exception as e:
        print(e)

输出:

Service1 running...
Service2 running...    
Service3 running...
All services freed.

Service1 running...
Service2 running...
Service3 failed!

未调用函数__exit__。 Service1和Service2不会释放。

我可以将_allocate_resource()移到__init__,但是在这种用法中,类不是很有用:

try:
    runner = ServiceRunner(allocation_success=True)
except Exception as e:
    print(e)
else:
    with runner as r:
        r.do()

    with runner as r:
        r.do()

输出:

Service1 running...
Service2 running...
Service3 running...
All services freed.
All services freed.

服务不会再次启动。

我可以重新实现__enter__来处理异常,但是它向该函数添加了一些样板代码:

def __enter__(self):
    try:
        self._allocate_resource()
    except Exception as e:
        self.__exit__(*sys.exc_info())
        raise e

这是最好的解决方案吗?

1 个答案:

答案 0 :(得分:1)

如果您未能输入上下文,则没有理由尝试退出它,即,如果您未能分配资源,则没有理由尝试释放它。

IIUC,您正在寻找的只是:

try:
   with ServiceRunner() as runner:
       runner.do()
except Exception as e:
    print(e)