关于python:在上下文处理程序中重新引发异常

re-raising an exception in a context handler

从上下文管理器上的datamodel文档:

Note that __exit__() methods should not reraise the passed-in exception; this is the caller’s responsibility.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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()

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

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

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

1
2
3
4
5
6
7
8
9
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?


  • __exit__返回True时,将吞下传递给它的任何异常。
  • __exit__返回False时,将重新启动该异常。
1
2
3
4
5
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的一个实例吗?如果是这样,

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

应该

1
with self.output() as output:

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

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


没有必要raise例外;该异常已经提出,您的上下文管理器只是被告知。

测试是否没有例外:

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

如果您的上下文管理器在那一刻返回True,您将禁止该异常;只是退出函数而不是返回None,异常仍然是"引发"。

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