Python多个非托管资源和异常安全

时间:2016-01-26 19:51:25

标签: python exception exception-handling

说我想拥有一个包含多个非托管资源的类,例如文件。我也喜欢该类的公共接口,允许用户以异常安全的方式使用它,即不泄漏非托管资源/让它们受垃圾收集器的支配,这不是垃圾收集器。 t保证在任何时候运行(确定性资源回收)。

例如,以下案例:

class Gizmo(object):
    def __init__(self, filepath1, filepath2):
        self._file1 = open(filepath1, 'rb')
        self._file2 = open(filepath2, 'rb')

    def __enter__(self):
        return self

    def __exit__(self):
        self.close()
        return False

    def __del__(self):
        self.close()

    def frob(self):
        ...manipulate the files...

    def close(self):
        self._file1.close()
        self._file2.close()

这不是异常安全的,因为如果在__init__中打开第二个文件的行失败,那么第一个文件就会泄漏,因为它留在垃圾收集器的左右(无论我是否是否通过with - 语句使用该类。)

我的问题是:实现我所希望的最简洁的方法是什么,最好是以我能够扩展到两个以上非托管资源的方式,以及并不会让我班级的公共界面变得太可怕(如果有的话)?我想到了一个与__init__方法分开的初始化方法的想法,但听起来有点奇怪。

1 个答案:

答案 0 :(得分:3)

如果您使用的是Python 3,这看起来像是contextlib.ExitStack的工作。如果您使用的是Python 2,则可能会有backport此功能。

from contextlib import ExitStack

class Gizmo(object):
    def __init__(self, filepath1, filepath2):
        with ExitStack() as stack:
            # If opening the second file fails,
            # unwinding the stack will close the first file.

            self._file1 = stack.enter_context(open(filepath1, 'rb'))
            self._file2 = stack.enter_context(open(filepath2, 'rb'))

            # It worked! But we don't want to close the files when the with statement ends.
            # Take the files off the stack without closing them
            # (and put them on a new stack).
            self._exitstack = stack.pop_all()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        return self._exitstack.__exit__(exc_type, exc_value, exc_tb)

    def __del__(self):
        self.close()

    def frob(self):
        ...manipulate the files...

    def close(self):
        # __del__ will try to close a Gizmo even if we couldn't set up its _exitstack,
        # so don't try to close a nonexistent _exitstack.
        if hasattr(self, '_exitstack'):
            # The stack calls __exit__ on all the files, exactly as if we were
            # exiting a "with open(...) as file1, open(...) as file2" block.
            # If closing one file fails, we'll still try to close the other.
            self._exitstack.close()