【问题标题】:re-raising an exception in a context handler在上下文处理程序中重新引发异常
【发布时间】:2013-08-26 06:13:55
【问题描述】:

来自上下文管理器上的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?

【问题讨论】:

  • 您真的是要在__exit__ 中关闭输出两次吗?
  • @user2357112,还有更多我没有在这里包含的代码 - 所以代码可能看起来很简洁,可能难以上下文化,抱歉。更新了我的问题,我没有关闭output_pipe 两次。

标签: python file exception resource-cleanup contextmanager


【解决方案1】:
  • __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: 任何 falsey 值都可以; None 包括在内;未显式返回意味着返回 None 并且仍然引发异常。
  • @MartijnPieters,我明白了。我现在发现明确的False 更容易接受。我必须检查标准库以了解这方面的约定...
【解决方案2】:

没有需要raise的异常; 已经引发了异常,而您的上下文管理器只是被告知。

测试是否有没有异常:

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

如果您的上下文管理器此时返回True,您将抑制异常;只是退出函数而不是返回 None 并且异常仍然“引发”。

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

【讨论】:

  • 这看起来不错,可以做我想做的事,但它是否释放了output_pipe 的资源(文件描述符),当时它是w 模式下的打开文件?
  • @miku:您的代码似乎调用了self.output_pipe.close() 两次;第二次只有当你没有遇到异常时。我只是展示了如何处理异常。
  • @user2357112:我在演示如何如果你需要测试的话。
  • @MartijnPieters,不,我没有关闭output_pipe 两次,我只是显示了两个变体(一个有错误检查,一个没有错误检查)。
  • @miku:对,那么在这种情况下你就不需要需要错误检查了。只需清理无论引发的异常。这就是上下文管理器的作用
猜你喜欢
  • 2014-06-06
  • 2013-02-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-30
  • 2011-08-27
相关资源
最近更新 更多