在Python中生成临时文件名,而不创建实际文件

166

回答Python中生成随机文件名的最佳方法的帖子显示了如何在Python中创建临时文件。

在我的情况中,我只需要一个临时文件名。
调用tempfile.NamedTemporaryFile()在实际创建文件后返回文件句柄。

有没有办法只获取文件名?我尝试过这个:

# Trying to get temp file path
tf = tempfile.NamedTemporaryFile()
temp_file_name = tf.name
tf.close()
# Here is my real purpose to get the temp_file_name
f = gzip.open(temp_file_name ,'wb')
...

7
NamedTemporaryFile 会保证生成一个唯一的文件名,它会通过尝试不同的文件名来确保唯一性。仅仅获得文件名并不能保证你之后能够创建这个文件,因为在你创建之前,其他人也有可能使用相同的名字创建了一个文件,这就存在竞态条件。 - Joachim Isaksson
7
@Joachim 的说法没错,这里确实存在竞态条件(race condition),最好能避免这种情况。然而,有时候你必须向函数传递一个临时文件名(文件打开是在内部进行的)。使用一个漂亮的随机名称会更大程度地减少竞态条件的影响。我认为提供一个好的临时文件名以最小化竞态条件故障的可能性是合理的需求。当然,根据正在运行的进程和任务添加一个好的前缀和后缀将进一步减少碰撞的机会。 - PolyMesh
@PolyMesh 你可以通过创建一个临时目录,然后在其中使用一个固定名称的文件来避免竞态条件。因此,你的函数接受一个目录而不是一个文件,并始终创建相同的文件。 - DylanYoung
1
@DylanYoung:虽然聪明,将工作隔离到临时目录仍会使您的代码面临竞争。即使您明确更改该目录的权限以排除竞争用户,您也永远无法排除超级用户或同一用户的竞争进程 - 当然,您只会引入更多微妙的竞争(例如,如果竞争进程在您有机会之前创建相同的临时目录并更改该目录的权限)。最好的方法可能是混淆临时文件名并紧握您的GNU手册。 - Cecil Curry
@CecilCurry 对啊,我当时在想什么呢。其实那就是同一个竞态条件,哈哈。不过我很惊讶 Python 或者操作系统居然没有做出防范措施…使用唯一的前缀或后缀肯定是更好的选择,只要你有一个唯一的标识符可供使用。 - DylanYoung
显示剩余2条评论
9个回答

106

我认为最简单、最安全的方法是这样的:

path = os.path.join(tempfile.mkdtemp(), 'something')

创建一个只有您可以访问的临时目录,因此不应该存在安全问题,但是在其中不会创建任何文件,因此您可以随意选择要在该目录中创建的任何文件名。请记住,在此之后您仍然必须删除该文件夹。

编辑:在Python 3中,现在可以使用tempfile.TemporaryDirectory()作为上下文管理器来处理删除:

with tempfile.TemporaryDirectory() as tmp:
  path = os.path.join(tmp, 'something')
  # use path

1
正如Daniel Braun所述:使用tempfile.mkdtemp时,重要的是记得在使用完后删除临时目录及其内容的责任由用户承担。 - bitinerant
17
如果你将tempfile.TemporaryDirectory()用作上下文管理器,它会被自动删除。 - gerrit
2
我非常喜欢这个功能,因为它可以自动处理删除。在我的情况下,它对于为shutil.make_archive函数创建临时文件名非常有用,因为该函数需要输入归档的基本名称,而不是类似文件的对象。 - Kota Mori

97

如果您只希望获得临时文件名,可以调用内部的tempfile函数_get_candidate_names()

import tempfile

temp_name = next(tempfile._get_candidate_names())
% e.g. px9cp65s

调用next会返回另一个名称,以此类推。这并不会为您提供到临时文件夹的路径。要获取默认的“tmp”目录,请使用:

defult_tmp_dir = tempfile._get_default_tempdir()
% results in: /tmp 

3
创建临时目录的更好方法是使用 tempfile.mkdtemp(prefix='some-prefix_'),它会安全地创建一个临时目录并返回该目录的绝对路径字符串。请注意,此函数可以指定前缀以便于标识临时目录。 - Emanuel Ey
6
需要指出的是,next(tempfile._get_candidate_names()) 不一定会返回一个不存在的路径,这就是为什么用户级别的 tempfile 接口可以尝试多个名称直到找到未使用的名称的原因。 - Eli Korvigo
5
可以使用公共的tempfile.gettempdir()而不是私有的tempfile._get_default_tempdir() - flonk
@EmanuelEy 使用tempfile.mkdtemp时,重要的是要记住用户需要在使用完毕后删除临时目录及其内容。 - Daniel Braun

13

tempfile.mktemp()可以完成这个功能。

但请注意,它已经被弃用了。 然而,与使用_get_candidate_names()相比,在tempfile中,它是一个公共函数且不会创建文件。

它被弃用的原因是由于调用此函数和尝试创建文件之间的时间差。 但在我的情况下,失败的可能性非常小,即使失败也是可以接受的。 但这取决于您对自己的用例进行评估。


1
即使失败也可以接受;竞争条件不仅是失败的风险,而且是安全风险(请参阅 tempfile.mktemp 文档)。因此,这不应被视为可接受的。 - bignose
8
@bignose 这是一个“潜在的”安全问题。这取决于您想要做什么,您所处的执行环境等因素。话虽如此:做类似于os.path.join(tempfile.mkdtemp(), 'something')的操作可能会更安全。至少在那里目录是创建的(而且我假设是由您拥有)。 - alecbz

11

可能有点晚了,但这个有什么问题吗?

import tempfile
with tempfile.NamedTemporaryFile(dir='/tmp', delete=False) as tmpfile:
    temp_file_name = tmpfile.name
f = gzip.open(temp_file_name ,'wb')

59
这段代码实际上会创建临时文件以获取其名称,而问题中则说“在Python中不创建实际文件”。 - Jakub Kukul
2
这并没有回答问题。 - herve
完美地为我工作。谢谢! - shargors

7

让我们尽量不要想得太多:

import os, uuid, tempfile as tf

def make_temp_name(dir = tf.gettempdir()):
    return os.path.join(dir, str(uuid.uuid1()))

6

结合之前的回答,我的解决方案是:

def get_tempfile_name(some_id):
    return os.path.join(tempfile.gettempdir(), next(tempfile._get_candidate_names()) + "_" + some_id)

如果不需要,可以将some_id设置为可选项。


候选人的名称可能实际上并不可用。这是正确的答案:https://dev59.com/jl8d5IYBdhLWcg3wsT4b#45803022 - j4hangir
1
然而,可能需要创建随机名称。不过,为了确保,如果_get_candidate_names()不存在,可以默认使用一些半随机字符串生成器。例如一些uuid。 - juanmirocks

5
如评论中Joachim Isaksson所说,如果只获取一个名称,在你的程序使用该名称之前其他程序可能已经使用了该名称。这种情况的概率很小,但不是不可能出现问题。
因此,在这种情况下,安全起见应该使用完整的GzipFile()构造函数,其签名为:
GzipFile( [filename[, mode[, compresslevel[, fileobj]]]])

所以你可以将打开的文件对象和文件名一起传递给它,如果你愿意的话。有关详细信息,请参见gzip文档


5

我会这样做:

import tempfile
import os.path
import random
import string

def generate_temp_filename() -> str:
    random_string = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10))
    return os.path.join(tempfile.gettempdir(), random_string)

相比其他答案的优点:

  • 不依赖于私有函数(以_开头的函数)
  • 不创建文件或目录
  • 不使用已弃用的函数

不错,年轻的帕达万。你仍在与竞争进程比赛,但至少你不再使用已弃用和/或私有的stdlib实用程序函数来破坏自己的代码库了。如果目录创建不是一个重要问题,我会稍微推荐使用tempfile.TemporaryDirectory()方法而不是这个。现在真的是选择你的毒药了。 - Cecil Curry
值得更多的点赞。这就是我所追求的。 - Contango

0
from random import sample
from string import digits, ascii_letters
from tempfile import gettempdir
from pathlib import Path

filename = Path(gettempdir()) / ''.join(sample(ascii_letters + digits, 10))
## PosixPath('/tmp/fHyMSeVsY8') or
## 
filename = Path(gettempdir()).joinpath(''.join(sample(ascii_letters + digits, 10))).as_posix()
## '/tmp/fHyMSeVsY8'

f = gzip.open(filename ,'wb')

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