在上下文处理程序中重新引发异常

时间:2013-08-23 08:51:24

标签: python file exception resource-cleanup contextmanager

来自datamodel docs上下文管理员:

  

请注意,__exit__()方法不应该重新引用传入的异常;这是来电者的责任。


我有一个临时文件,我想用close释放其文件描述符,但是没有写任何东西到磁盘。我的直观解决方案是传递异常,但那是discouraged in the docs - 当然有充分的理由。

class Processor(object):
    ...
    def write(self, *args, **kwargs):
        if something_bad_happens:
            raise RuntimeError('This format expects %s columns: %s, got %s.' % (
                               (len(self.cols), self.cols, len(args))))
        self.writer.writerow(args)

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        # the RuntimeError from write will be set as type, value and so on ..
        # I'd like to close the stream here (release the file descriptor), 
        # but I do not leave a trace of the associated file - 
        # (one can always 'manually' delete with `os.remove` but maybe there's a 
        # better way ..?)
        self.output_pipe.close()

此外,我不希望在此特定情况下调用者中的错误处理有两个原因:

  • 保持调用者的代码最小化(见下文)
  • 调用者对异常感到满意(快速失败就是我们想要的)

上下文管理器的用法如下:

class Worker(object):
    ...
    def run(self):
        # output setup so it will emit a three column CSV
        with self.output().open('w') as output:
            output.write('John', 'CA', 92101)
            output.write('Jane', 'NY', 10304)
            # should yield an error, since only three 'columns' are allowed 
            output.write('Hello', 'world')

更新:我的问题有点不合理,因为我的问题真的归结为:在嵌套的上下文管理器中,如何将异常传递给最外层的CM?

2 个答案:

答案 0 :(得分:9)

  • __exit__返回True时,将吞下传递给它的任何异常。
  • __exit__返回False时,将重新启动该异常。

def __exit__(self, type, value, traceback):
    self.output_pipe.close()  # always close the file
    if type is not None: # an exception has occurred
        os.unlink(...)   # remove the file
        return False     # reraise the exception

您当然可以省略return False,因为Python默认会返回None(这是Falsish)。


顺便说一下,self.output()Processor的一个实例吗?如果是的话,

with self.output().open('w') as output:

应该是

with self.output() as output:

在任何情况下,如果你可以安排后者是正确的语法会更好。您可能需要将__enter__更改为:

def __enter__(self):
    return self.output_pipe.open('w')

答案 1 :(得分:2)

没有需要raise例外;该异常已经已经被引发,并且您的上下文管理器仅被告知该情况。

测试是否有 no 例外:

if type is None:
    # if no exception is raised, proceed as usual:
    self.output_pipe.close()

如果您的上下文管理员要在那一刻返回True,您将取消该异常;只是退出函数而不是返回None,异常仍然是“被提升”。

请注意,tempfile module包含两种类型的临时文件对象,它们充当上下文管理器,自行删除已经,独立于平台。在POSIX系统上,您可以在创建后立即取消链接文件;在关闭文件之前,文件描述符仍然有效。 Windows也提供“关闭时删除”选项。 tempfile.NamedTemporaryFile()类为您的平台使用正确的选项。