如何创建一个可以被子进程读取的临时文件?

67
我正在编写一个Python脚本,需要将一些数据写入临时文件,然后创建一个运行C++程序的子进程来读取临时文件。我尝试使用NamedTemporaryFile实现此功能,但根据文档,不同平台上可以打开命名临时文件的次数不同(在Unix上可以这样使用,在Windows NT或更高版本上无法这样使用)。确实,在Windows上,如果我在写入后刷新临时文件,但在想让其消失之前没有关闭它,则子进程无法打开它进行读取。我通过使用delete=False创建文件,在生成子进程之前关闭它,然后在完成后手动删除它来解决这个问题:
fileTemp = tempfile.NamedTemporaryFile(delete = False)
try:
    fileTemp.write(someStuff)
    fileTemp.close()
    # ...run the subprocess and wait for it to complete...
finally:
    os.remove(fileTemp.name)

这看起来不太优雅。有没有更好的方法来做这个?也许有一种方法可以打开临时文件的权限,以便子进程可以访问它?

你考虑过将进程作为可能性进行管道传输吗? - Jon Clements
@JonClements 是的,管道传输也是可能的,但这将需要更改子进程的接口,而我希望避免这种情况。 - Nathan Reed
2
相关:Python问题14243 - 在Windows上tempfile.NamedTemporaryFile不是特别有用 - Piotr Dobrogost
另外还有一个针对Linux的解决方案:我可以在Python中为tempfile.NamedTemporaryFile设置umask吗? - nealmcb
6个回答

27

根据Richard Oudkerk的说法

(...) 在Windows上尝试重新打开一个NamedTemporaryFile失败的唯一原因是因为当我们重新打开时,需要使用O_TEMPORARY

他给出了Python 3.3+中如何实现这个示例。

import os, tempfile

DATA = b"hello bob"

def temp_opener(name, flag, mode=0o777):
    return os.open(name, flag | os.O_TEMPORARY, mode)

with tempfile.NamedTemporaryFile() as f:
    f.write(DATA)
    f.flush()
    with open(f.name, "rb", opener=temp_opener) as f:
        assert f.read() == DATA

assert not os.path.exists(f.name)

因为Python 2.x内置的open()函数中没有opener参数,所以我们需要结合更低级别的os.open()os.fdopen()函数来实现相同的效果。
import subprocess
import tempfile

DATA = b"hello bob"

with tempfile.NamedTemporaryFile() as f:
    f.write(DATA)
    f.flush()

    subprocess_code = \
    """import os
       f = os.fdopen(os.open(r'{FILENAME}', os.O_RDWR | os.O_BINARY | os.O_TEMPORARY), 'rb')
       assert f.read() == b'{DATA}'
    """.replace('\n', ';').format(FILENAME=f.name, DATA=DATA)

    subprocess.check_output(['python', '-c', subprocess_code]) == DATA

12
请注意,这仅在您控制子进程中的代码时才有效。调用诸如“tar”之类的系统实用程序并不会轻松地起作用。不过,这是个好发现! - Dave Dopson
这很有趣,因为文档中说os.O_TEMPORARY仅适用于Linux:http://bit.ly/1MmDUxz也许他们已经在Windows中添加了对该值的支持,而NamedTemporaryFile的Python实现尚未针对Windows进行更新? - yucer
1
@yucer 请仔细查看,您展示的每组常量的标签位于该组下方而不是上方。 - Piotr Dobrogost
@yucer 我从未听说过这样的PEP,我认为并不存在这样的PEP。 - Piotr Dobrogost
4
如果您控制子进程的代码,那么这是最好的解决方案。如果不是,我认为原问题中的解决方案已经很优雅了。 - Marcin Raczyński
显示剩余4条评论

26

既然没有人似乎有兴趣将此信息公开...

tempfile确实暴露了一个函数mkdtemp(),可以轻松解决这个问题:

try:
    temp_dir = mkdtemp()
    temp_file = make_a_file_in_a_dir(temp_dir)
    do_your_subprocess_stuff(temp_file)
    remove_your_temp_file(temp_file)
finally:
    os.rmdir(temp_dir)

我将中间函数的实现留给读者,因为有人可能希望做一些像使用mkstemp()来加强临时文件本身的安全性或在删除之前就地覆盖文件等事情。我不是特别清楚有什么安全限制是无法通过查阅tempfile源代码轻松规划的。
无论如何,在Windows上使用NamedTemporaryFile可能是不雅的,而我在这里提供的解决方案也可能是不雅的,但您已经决定Windows支持比优雅的代码更重要,所以最好做一些可读性更好的事情。

3
清理代码可使用 shutil.rmtree(temp_dir) 进行简化。 - Dave Dopson
5
注意!这完全没有回答原问题。不仅如此,这比 OP 已经在做的更糟,因为它将创建临时文件的脆弱方面留给了用户——make_a_file_in_a_dir(temp_dir)。请查看我的答案,以了解处理方式更好的方法。(我不明白为什么我在四年前第一次阅读这篇文章时没有写下这条评论...)。 - Piotr Dobrogost
2
下降票。尽管此答案提出了原始问题的解决方案(在子进程中打开临时文件),但这并不比原始问题中包含的答案更优雅,因为此答案使用了较低级别的函数并忽略了一般的临时文件问题(例如安全性)。 - Marcin Raczyński
1
本地变量'temp_dir'在赋值之前被引用。我猜实际的创建必须放在“try”之外,这使得调试变得困难? - Hugues Fontenelle
2
我刚试了一下,我的版本不起作用。创建临时文件的代码是:cert_file = tempfile.NamedTemporaryFile(dir=temp_dir) - hum3
显示剩余2条评论

12
你总可以去使用底层编程,但我不确定它是否足够干净:

fd, filename = tempfile.mkstemp()
try:
    os.write(fd, someStuff)
    os.close(fd)
    # ...run the subprocess and wait for it to complete...
finally:
    os.remove(filename)

11
至少,如果您使用现有的Python库打开临时文件,则在Windows的情况下无法从多个进程访问它。根据MSDN,您可以为CreateFile()函数指定第三个参数(dwSharedMode)共享模式标志FILE_SHARE_READ

启用对文件或设备的后续打开操作请求读取访问权限。否则,如果其他进程请求读取访问权限,则它们无法打开文件或设备。如果未指定此标志,但是已经打开了文件或设备以进行读取访问,则函数失败。

因此,您可以编写一个特定于Windows的C例程来创建自定义临时文件打开器函数,从Python调用它,然后您可以使子进程访问该文件而不会出现任何错误。但是我认为您应该坚持现有的方法,因为它是最可移植的版本,并且将在任何系统上运行,因此是最优雅的实现。
  • 有关Linux和Windows文件锁定的讨论可以在here找到。

编辑:事实证明,在Windows中也可以从多个进程打开和读取临时文件。请参见Piotr Dobrogost的answer


谢谢。我希望Python API能够公开这些模式标志,但我可以理解为什么他们不这样做。哦,算了。 - Nathan Reed
至少在使用现有的Python库打开临时文件时,在Windows情况下无法从多个进程访问它是不正确的。这不是真的。请参见http://bugs.python.org/issue14243#msg164504和我的答案https://dev59.com/42Up5IYBdhLWcg3wmYYt#15235559。 - Piotr Dobrogost

1

我知道这是一个非常老的帖子,但我认为它在今天仍然相关,因为API正在改变,像mktemp和mkstemp这样的函数正在被类似TemporaryFile()和TemporaryDirectory()的函数所取代。我只想在以下示例中演示如何确保临时目录在下游仍然可用:

不要编写以下代码:

tmpdirname = tempfile.TemporaryDirectory()

如果你在代码中使用了tmpdirname,那么你应该尝试将你的代码放在一个with语句块中,以确保它对你的代码调用可用...就像这样:

with tempfile.TemporaryDirectory() as tmpdirname:
    [do dependent code nested so it's part of the with statement]

如果您在 with 语句之外引用它,那么很可能它将不再可见。

原始问题是关于临时文件,而不是临时目录。你有临时文件的例子吗? - howdoicode

0

在编写代码时,使用mkstemp()代替os.fdopen()并将其放在with语句中,可以避免调用close()方法:

fd, path = tempfile.mkstemp()
try:
    with os.fdopen(fd, 'wb') as fileTemp:
        fileTemp.write(someStuff)
    # ...run the subprocess and wait for it to complete...
finally:
    os.remove(path)

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