2

通常我使用 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(). 这个函数返回一个打开文件的句柄和一个路径名,所以openwith语句中使用是错误的。

我搜索了一下,发现了很多关于小心mkstemp正确使用的警告。几篇博客文章说不要扔掉mkstemp. 有关于操作系统级文件句柄与 Python 级文件对象不同的讨论。很好,但我找不到最简单的编码模式来确保

  • mkstemp被调用以获取要写入的文件
  • 编写完成后,即使发生异常,Python 文件及其底层 os 文件句柄也会完全关闭。这正是我们可以通过with(open...模式获得的那种行为。

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

4

1 回答 1

12

简单的编码模式是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 库有办法在数据到达时提供数据,许多下载库有办法一点一点地流式传输数据——正如您可能想象的那样,这对于 cElementTreeXMLParserrequestsin几种不同的方式。例如:

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()
于 2014-01-03T21:26:58.130 回答