如何在不关闭资源的情况下离开`with`块?

4

我正试图实现类似的功能

from tempfile import TemporaryFile

def open_head(file_path):
   with open(file_path, 'r') as f,
        TemporaryFile() as tf:
       for i in range(0,10):
           tf.write(f.read_line())
       return tf

以便调用者获得临时文件的所有权。

特别是,我不希望with语句关闭TemporaryFile。但是如果在return之前出现任何问题,我仍然希望 TemporaryFile with 语句关闭。

理想情况下,我想将调用者编写为

with open_head(file_path):
    # more code here

这是否有可能?例如,通过编写return do_not_close(tf)或其他实用功能来实现?

或者我的方法完全错误,有更加Pythonic的方式在函数之间返回TemporaryFiles或其他资源,并保证异常安全性?


4
with语句的整个意义在于自动为您关闭文件。如果这不是您想要的,请不要使用它。 - Mark Ransom
3个回答

7
你不需要这样做。 open_head 应该接受一个已经打开的句柄,而这个 调用者 负责关闭它。
from tempfile import TemporaryFile
from itertools import islice


def head(file_path, fh):
    with open(file_path) as f:
        for line in islice(f, 10):
            fh.write(line)


with TemporaryFile() as tf:
    head(file_path, tf)
    # Do other stuff with tf before it gets closed.
    

一般来说,当你在函数中打开文件时,请问自己是否可以将实际的打开推迟到调用者,并接受类似文件的对象。除了使代码更具可重用性外,还使代码更易于测试。head不必使用实际文件进行调用:它可以使用任何类似文件的对象,如io.StringIO
换句话说:with语句强制执行以下建议:

如果你打开了文件,你也要负责关闭它。

该建议的逆命题是:

如果你不负责关闭文件,那么你也不用负责打开文件。


1

只需将TemporaryFile移出上下文管理器并用try except块包装即可。

from tempfile import TemporaryFile

def open_head(path: str):
    try:
        tf = TemporaryFile()
        with open(path, "r") as f:
            for _ in range(10):
                tf.write(f.readline())
            return tf
    except Exception as e:
        tf.close()
        raise e

4
在那个except块中或许需要加上一条raise语句,这样我们就不会丢弃异常了。 - Charles Duffy
1
但现在你失去了 tf 将被正确关闭的保证。你打开了它,但你不知道谁会关闭它,如果有人的话。 - chepner

0

我可能误解了你的问题,但是你可以明确地打开临时文件,从函数中返回它,然后在需要的时候关闭它吗?

def open_head(...):
   with open(file_path, 'r') as f:
      tf = TemporaryFile()
      for i in range(0,10):
         tf.write(f.read_line())
   return tf

tf = open_head(...)

# do some work

tf.close() # close the temporary file

请确保你明白这一点——通过从 open_head 函数中返回 tf,你自己需要负责正确关闭它。

with 关键字用于上下文管理器,其具有 __enter____exit__ 方法。当你离开 with 块时,将调用 __exit__ 方法,这将关闭文件描述符。

如果你想处理异常,可以使用 yield 临时文件描述符。

@contextlib.contextmanager
def open_head(...):
    tf = TemporaryFile()

    try:
        # do some work that might cause errors
        yield tf
    finally:
        os.close(tf)

是的,这就是我目前正在做的。这种解决方案的问题在于,如果例如 f.read_line() 引发异常,TemporaryFile 将会泄漏。我正在寻找一种安全的异常处理方式,最好不使用 try-catch - Vogelsgesang
@Vogelsgesang 你确定需要为此使用try catch吗?TemporaryFile的文档(https://docs.python.org/3/library/tempfile.html#tempfile.TemporaryFile)中写道:“它将在关闭时被销毁(包括对象垃圾回收时的隐式关闭)。”。因此,如果出现异常,则函数执行会被中断,局部变量`tf`超出范围,被垃圾回收并隐式关闭。 - Yu Chen
@Vogelsgesang 请查看此示例 - https://gist.github.com/ychennay/319e14f35becb93b556e6637c7f5ce8b。临时文件在超出范围后立即关闭。但是,当您从`open_head`函数返回它时,您自己负责关闭它。 - Yu Chen

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