有没有办法自动关闭mkstemp()返回的Python临时文件

时间:2014-01-03 21:21:16

标签: python mkstemp

通常我使用with语句在Python中处理文件,就像在这个块中通过HTTP下载资源一样:

with (open(filename), "wb"):
    for chunk in request.iter_content(chunk_size=1024):
        if chunk:
            file.write(chunk)
            file.flush()

但这假设我知道文件名。假设我想使用tempfile.mkstemp()。此函数返回打开文件的句柄和路径名,因此在open语句中使用with将是错误的。

我搜索了一下,发现了许多关于小心谨慎使用mkstemp的警告。几篇博客文章在他们说不丢弃mkstemp返回的整数时几乎大喊大叫。有关os级文件句柄与Python级文件对象不同的讨论。这没关系,但我找不到能确保

的最简单的编码模式
    调用
  • mkstemp来获取要写入的文件
  • 写完后,即使出现异常,Python文件及其底层的os文件句柄也会干净地关闭。这正是我们使用with(open...模式可以获得的行为。

所以我的问题是,在Python中有一种很好的方法来创建和写入mkstemp生成的文件,可能使用不同类型的状态,或者我必须手动执行{{1或者fdopen等等。似乎应该有一个明确的模式。

1 个答案:

答案 0 :(得分:11)

最简单的编码模式为try: / finally:

fd, pathname = tempfile.mkstemp()
try:
    dostuff(fd)
finally:
    os.close(fd)

但是,如果您不止一次这样做,将它包装在上下文管理器中是微不足道的:

@contextlib.contextmanager
def mkstemping(*args):
    fd, pathname = tempfile.mkstemp(*args)
    try:
        yield fd
    finally:
        os.close(fd)

然后你可以这样做:

with mkstemping() as fd:
    dostuff(fd)

当然,如果你真的想要,你总是可以将fd包装在文件对象中(通过将其传递给open或旧版本中的os.fdopen)。但是......为什么要去额外的麻烦?如果你想要一个fd,请将它用作fd。

如果你想要一个fd,除非你有充分的理由需要mkstemp而不是更简单和更高级NamedTemporaryFile, 你不应该使用低级API。就这样做:

with tempfile.NamedTemporaryFile(delete=False) as f:
    dostuff(f)

除了对with更简单之外,它还具有以下优点:它已经是Python文件对象而不仅仅是OS文件描述符(并且在Python 3.x中,它可以是Unicode文本文件)。


更简单的解决方案是完全避免临时文件。

几乎所有的XML解析器都有办法解析字符串而不是文件。使用cElementTree,只需要调用fromstring而不是parse。所以,而不是:

req = requests.get(url)
with tempfile.NamedTemporaryFile() as f:
    f.write(req.content)
    f.seek(0)
    tree = ET.parse(f)

......就这样做:

req = requests.get(url)
tree = ET.fromstring(req.content)

当然第一个版本只需要将XML文档和解析后的树一个接一个地保存在内存中,而第二个版本需要同时保存它们,这样可能会使峰值内存使用量增加约30%。但这很少成为问题。

如果 出现问题,许多XML库都可以在数据到达时提供数据,许多下载库都可以逐位传输数据 - 正如您可能想象的那样对于cElementTree的XMLParsera few different ways中的requests,情况也是如此。例如:

req = requests.get(url, stream=True)
parser = ET.XMLParser()
for chunk in iter(lambda: req.raw.read(8192), ''):
    parser.feed(chunk)
tree = parser.close()

并不像使用fromstring那么简单......但它仍然比使用临时文件更简单,并且启动效率可能更高。

如果使用iter的双参数形式会让你感到困惑(很多人一开始很难理解它),你可以将其重写为:

req = requests.get(url, stream=True)
parser = ET.XMLParser()
while True:
    chunk = req.raw.read(8192)
    if not chunk:
        break
    parser.feed(chunk)
tree = parser.close()