Python: 如何创建一个唯一的文件名?

125
我有一个Python Web表单,包含两个选项 - 文件上传文本框。我需要获取每个选项的值并传递给另一个命令行程序。对于文件上传选项,我可以轻松地传递文件名,但是我不确定如何传递文本框的值。
我认为我需要做的是:
  1. 生成唯一的文件名
  2. 在工作目录中使用该名称创建临时文件
  3. 将从文本框传递过来的值保存到临时文件中
  4. 从我的Python模块内部执行命令行程序,并将其命名为临时文件
我不确定如何生成唯一的文件名。有人能给我一些关于如何生成唯一文件名的提示吗?任何算法,建议和代码行都将不胜感激。
感谢您的关注。

1
我编辑了你的问题,试图让它更清晰明了。如果我有任何错误的理解,请告诉我! - culix
9个回答

182

我觉得你的问题不是很清楚,但如果你只需要一个独特的文件名……

import uuid

unique_filename = str(uuid.uuid4())

抱歉,我正在使用Windows平台工作,不知道如何处理子进程。 - MysticCodes
uuid 似乎会创建一个很长的唯一字符串。我认为在文件名中使用长字符串和 UUID 不是更好的选择,()。 - MysticCodes
创建临时文件的机制类似于使用tempfile.mktemp,因此也容易受到攻击: “在你开始处理返回的文件名之前,其他人可能已经抢先一步了。”建议使用tempfile模块中的其他方法。 - Simón
12
我认为 uuid.uuid4().hex 是更好的选择,详见这里 - Grey Li
4
你的电脑 CPU 出现处理错误,导致它加载了错误的文件,这种情况比生成的 UUID 与任何现有值重复发生的可能性更大。UUID 在计算模型中生成唯一名称,该模型理解并不是所有的计算都是纯数学。 - GManNickG
2
请原谅我的无知,旧的评论... 实际上不是唯一的,但很不可能发生冲突,所以选择很好 ;) - Tolo Palmer

57
如果您想在Python中创建临时文件,可以使用Python标准库中的tempfile模块。如果您想启动其他程序来操作文件,则可以使用tempfile.mkstemp()创建文件,并使用os.fdopen()访问mkstemp()提供给您的文件描述符。
顺便提一下,您说您正在从Python程序中运行命令?您几乎肯定应该使用subprocess模块。
因此,您可以轻松编写类似以下代码的代码:
import subprocess
import tempfile
import os

(fd, filename) = tempfile.mkstemp()
try:
    tfile = os.fdopen(fd, "w")
    tfile.write("Hello, world!\n")
    tfile.close()
    subprocess.Popen(["/bin/cat", filename]).wait()        
finally:
    os.remove(filename)

运行后,您应该发现cat命令完美地工作了,但是临时文件在finally块中被删除。请注意,您必须自己删除mkstemp()返回的临时文件 - 库无法知道何时完成使用!(编辑:我曾经认为NamedTemporaryFile正好符合您的要求,但这可能不太方便-当关闭临时文件对象时,文件将立即被删除,并且在您关闭它之前让其他进程打开文件在某些平台上无法工作,特别是Windows。对不起,这是我的失误。)

我能让临时文件名也是唯一的吗?这样在子进程完成后,我就可以用唯一的名称保存它了。 - MysticCodes
@Terence Honles: 我最初建议使用tempfile.NamedTemporaryFile(),但你不能在Windows上用它来创建其他进程可以访问的临时文件。不过,NamedTemporaryFile(delete=False)肯定更简洁。@user343934: tempfile.mkstemp()保证每次调用时都会给你一个独特的名称-它随机生成名称,并使用操作系统设施(如果你想知道的话,是O_EXCL)来避免冲突。 - Richard Barrell
哇,我不知道它在Windows上不工作…失败了 :( …我想那是好事要知道的。 - Terence Honles
@ Richard,当它提供“C:/wamp/www/project”而不是“/bin/cat”时,它显示访问被拒绝。 - MysticCodes
@user343934 /bin/cat是Unix上一个始终存在的程序名称。我加上它是因为我忘记了人们使用的计算机不一定是Mac ;)你可以用在Windows上可行的命令替换它。例如,将整个subprocess.Popen行替换为: subprocess.Popen(["cmd.exe", "/c", "type", filename]).wait()我相信这会起作用,但我手边没有带有Python的Windows盒子来测试。 - Richard Barrell
显示剩余2条评论

56

uuid模块是一个不错的选择,我更喜欢使用uuid.uuid4().hex作为随机文件名,因为它会返回没有破折号的十六进制字符串

import uuid
filename = uuid.uuid4().hex

输出应该像这样:

>>> import uuid
>>> uuid.uuid()
UUID('20818854-3564-415c-9edc-9262fbb54c82')
>>> str(uuid.uuid4())
'f705a69a-8e98-442b-bd2e-9de010132dc4'
>>> uuid.uuid4().hex
'5ad02dfb08a04d889e3aa9545985e304'  # <-- this one

3
翻译:有短划线会有什么问题? - David Lopez
1
添加“.hex”是出于美观考虑还是有其他原因? - simanacci
3
通常在文件名中使用短横线来分隔单词(例如my-favorite-shoes.jpg)。然而,如果文件名是随机生成的,我更喜欢没有短横线的文件名,这样更美观,这些短横线在这里没有意义。 - Grey Li

20

也许你需要一个独特的临时文件?

import tempfile

f = tempfile.NamedTemporaryFile(mode='w+b', delete=False)

print f.name
f.close()
delete=False表示在关闭后不删除文件。

如果需要控制文件名,可以使用可选的prefix=...suffix=...参数,接受字符串。请参见https://docs.python.org/3/library/tempfile.html


如果您不需要控制文件名,那么这很棒。 - hiwaylon
1
应该使用tmpfile.NamedTemporaryFile而不是仅使用NamedTemporaryFile。 - user1993015
w+b 是默认的 mode。使用任何 tempfile 功能都有不正确的文件访问权限的缺点:tempfile 文档建议使用 os.O_TMPFILE 作为掩码,但普通文件创建会尊重 os.umask() - m8mble

15
您可以使用 datetime 模块。
import datetime
uniq_filename = str(datetime.datetime.now().date()) + '_' + str(datetime.datetime.now().time()).replace(':', '.')

请注意: 我使用replace是因为在许多操作系统中文件名中不允许使用冒号。

就这样,这将为您每次都提供一个唯一的文件名。


4
除非文件名是连续创建的(例如在循环中),否则它们是不同的。 - skjerns

7

如果您需要将短唯一标识作为文件名,请尝试使用shortuuidshortuuid 使用小写字母、大写字母和数字,并删除类似的字符,如 l、1、I、O 和 0。

>>> import shortuuid
>>> shortuuid.uuid()
'Tw8VgM47kSS5iX2m8NExNa'
>>> len(ui)
22

相比于

>>> import uuid
>>> unique_filename = str(uuid.uuid4())
>>> len(unique_filename)
36
>>> unique_filename
'2d303ad1-79a1-4c1a-81f3-beea761b5fdf'

1
我发现了这个问题,并为那些可能正在寻找类似解决方案的人添加了我的解决方案。我的方法只是从ASCII字符中制作一个随机文件名。它有很高的唯一性概率。
from random import sample
from string import digits, ascii_uppercase, ascii_lowercase
from tempfile import gettempdir
from os import path

def rand_fname(suffix, length=8):
    chars = ascii_lowercase + ascii_uppercase + digits

    fname = path.join(gettempdir(), 'tmp-'
                + ''.join(sample(chars, length)) + suffix)

    return fname if not path.exists(fname) \
                else rand_fname(suffix, length)

1
这个问题的显而易见的答案与uuid包有关。然而,我的目标服务器只有Python 2.4版本,没有uuid包,并且由于遗留软件不兼容性,升级也未经服务器所有者授权,因此这个答案对我很有效。 - Alberto Gaona
1
我特别喜欢这个答案:可以轻松地根据项目规格进行调整。 - swdev
1
  1. 在这里使用递归没有任何理由,特别是无限制的递归。
  2. path.exists()返回False和消费者实际打开文件之间存在竞争条件。
- Jonathon Reinhart

1
为了创建一个唯一的文件路径,如果它已经存在,可以使用随机包来生成一个新的字符串名称。您可以参考下面的代码实现。
import os
import random
import string

def getUniquePath(folder, filename):    
    path = os.path.join(folder, filename)
    while os.path.exists(path):
         path = path.split('.')[0] + ''.join(random.choice(string.ascii_lowercase) for i in range(10)) + '.' + path.split('.')[1]
    return path

现在你可以使用这个路径相应地创建文件。

该文件仍有可能随机更改现有名称。 - Limtis

0
可以使用 ufp.path 模块中的 unique 函数来完成此操作。
import ufp.path
ufp.path.unique('./test.ext')

如果当前路径存在'test.ext'文件,ufp.path.unique函数将返回'./test (d1).ext'。

7
UFP是Drupal的一部分吗?它不是标准模块。 - endolith

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