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

13

根据上下文管理器的 数据模型文档

请注意,__exit__() 方法不应该重新引发传递进来的异常,这应该由调用者负责。


我有一个临时文件,我想用 close 方法释放它的文件描述符,但是不想将任何内容写入磁盘。我的直觉解决方案是传递异常,但是文档中不鼓励使用,肯定有充分的理由。

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')
更新:我的问题表述有些不清晰,实际上我的问题可以归结为以下内容:在嵌套的上下文管理器中,如何将异常传递给最外层的上下文管理器?

1
你真的想在 __exit__ 中两次关闭输出吗? - user2357112
@user2357112,这里还有更多的代码我没有包含在内 - 所以代码可能看起来很简洁,也许很难理解上下文,对此我很抱歉。我更新了我的问题,我没有两次关闭 output_pipe - miku
2个回答

19
  • __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(它被视为假)。


顺便问一下,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')

谢谢,我猜 return False 就是我要找的。 - miku
3
任何假值都可以使用,包括 None;不显式返回意味着返回 None 并且异常仍然被抛出。 - Martijn Pieters
1
@MartijnPieters,我明白了。目前我认为显式的False更易读一些。我需要查看标准库中对此的约定…… - miku

3

不需要触发异常;异常已经被触发,您的上下文管理器只是被告知了这一点。

测试是否没有异常:

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

如果您的上下文管理器在那个时刻返回True,则会抑制异常;而仅仅退出函数则返回None,异常仍然“被引发”。

请注意,tempfile模块包括两种临时文件对象类型,它们作为上下文管理器自我删除,与平台无关。在POSIX系统上,您可以在创建后立即取消链接文件;文件描述符保持活动状态,直到关闭文件。Windows也提供了“关闭后删除”的选项。tempfile.NamedTemporaryFile()类使用适合您平台的正确选项。


那看起来不错并且符合我的需求,但它是否释放了output_pipe的资源(文件描述符),因为此时该管道以“w”模式打开? - miku
@miku:看起来你的代码调用了 self.output_pipe.close() 两次;第二次只有在没有遇到异常时才会调用。我只是展示了如何处理异常。 - Martijn Pieters
@user2357112:我正在演示如果需要的话,你应该如何进行测试。 - Martijn Pieters
@MartijnPieters,我没有两次关闭output_pipe,我只是展示了两个变体(一个带有错误检查,一个不带)。 - miku
@miku:没错,在这种情况下,你不需要错误检查。无论是否引发异常,都要进行清理。这就是上下文管理器的作用所在。 - Martijn Pieters
@MartijnPieters,我现在意识到我的问题有点不完整。无论如何,还是谢谢你的回答。 - miku

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接