如何在 Python 中创建递增的文件名?
Posted
技术标签:
【中文标题】如何在 Python 中创建递增的文件名?【英文标题】:How do I create an incrementing filename in Python? 【发布时间】:2013-08-01 19:55:34 【问题描述】:我正在创建一个程序,它将创建一个文件并将其保存到文件名为 sample.xml 的目录中。一旦文件被保存,当我再次尝试运行程序时,它会将旧文件覆盖到新文件中,因为它们确实具有相同的文件名。如何增加文件名,以便每当我尝试再次运行代码时,它都会增加文件名。并且不会覆盖现有的。我正在考虑先检查目录上的文件名,如果它们相同,代码将生成一个新文件名:
fh = open("sample.xml", "w")
rs = [blockresult]
fh.writelines(rs)
fh.close()
【问题讨论】:
【参考方案1】:从给定的文件名继续序列编号,带或不带附加的序列号。
如果给定的文件名不存在,将使用它,否则应用序列号,并且数字之间的间隙将是候选。
如果给定的文件名尚未排序或者是顺序最高的现有文件,则此版本很快。
例如,提供的文件名可以是
sample.xml sample-1.xml sample-23.xmlimport os
import re
def get_incremented_filename(filename):
name, ext = os.path.splitext(filename)
seq = 0
# continue from existing sequence number if any
rex = re.search(r"^(.*)-(\d+)$", name)
if rex:
name = rex[1]
seq = int(rex[2])
while os.path.exists(filename):
seq += 1
filename = f"name-seqext"
return filename
【讨论】:
【参考方案2】:这里还有一个例子。代码测试目录中是否存在文件,如果存在,它会在文件名的最后一个索引中增加并保存 典型的文件名是:month_date_lastindex.txt 的三个字母 ie.e.g.May10_1.txt
import time
import datetime
import shutil
import os
import os.path
da=datetime.datetime.now()
data_id =1
ts = time.time()
st = datetime.datetime.fromtimestamp(ts).strftime("%b%d")
data_id=str(data_id)
filename = st+'_'+data_id+'.dat'
while (os.path.isfile(str(filename))):
data_id=int(data_id)
data_id=data_id+1
print(data_id)
filename = st+'_'+str(data_id)+'.dat'
print(filename)
shutil.copyfile('Autonamingscript1.py',filename)
f = open(filename,'a+')
f.write("\n\n\n")
f.write("Data comments: \n")
f.close()
【讨论】:
【参考方案3】:我的 2 美分:一个不断增加的 macOS 风格的增量命名程序
get_increased_path("./some_new_dir").mkdir()
创建 ./some_new_dir
;那么
get_increased_path("./some_new_dir").mkdir()
创建 ./some_new_dir (1)
;那么
get_increased_path("./some_new_dir").mkdir()
创建 ./some_new_dir (2)
;等
如果./some_new_dir (2)
存在但不存在 ./some_new_dir (1)
,那么get_increased_path("./some_new_dir").mkdir()
无论如何都会创建./some_new_dir (3)
,这样索引总是会增加并且你总是知道哪个是最新的
from pathlib import Path
import re
def get_increased_path(file_path):
fp = Path(file_path).resolve()
f = str(fp)
vals = []
for n in fp.parent.glob("*".format(fp.name)):
ms = list(re.finditer(r"^ \(\d+\)$".format(f), str(n)))
if ms:
m = list(re.finditer(r"\(\d+\)$", str(n)))[0].group()
vals.append(int(m.replace("(", "").replace(")", "")))
if vals:
ext = " ()".format(max(vals) + 1)
elif fp.exists():
ext = " (1)"
else:
ext = ""
return fp.parent / (fp.name + ext + fp.suffix)
【讨论】:
使用 python 3.5 尝试了代码,有一些错误,结果也没有删除文件扩展名,它只是将文件扩展名添加到整个文件名。【参考方案4】:顺序检查每个文件名以查找下一个可用的文件名适用于少量文件,但随着文件数量的增加很快会变慢。
这是一个在 log(n) 时间内找到下一个可用文件名的版本:
import os
def next_path(path_pattern):
"""
Finds the next free path in an sequentially named list of files
e.g. path_pattern = 'file-%s.txt':
file-1.txt
file-2.txt
file-3.txt
Runs in log(n) time where n is the number of existing files in sequence
"""
i = 1
# First do an exponential search
while os.path.exists(path_pattern % i):
i = i * 2
# Result lies somewhere in the interval (i/2..i]
# We call this interval (a..b] and narrow it down until a + 1 = b
a, b = (i // 2, i)
while a + 1 < b:
c = (a + b) // 2 # interval midpoint
a, b = (c, b) if os.path.exists(path_pattern % c) else (a, c)
return path_pattern % b
为了衡量速度的提高,我编写了一个创建 10,000 个文件的小测试函数:
for i in range(1,10000):
with open(next_path('file-%s.foo'), 'w'):
pass
并实现了幼稚的方法:
def next_path_naive(path_pattern):
"""
Naive (slow) version of next_path
"""
i = 1
while os.path.exists(path_pattern % i):
i += 1
return path_pattern % i
结果如下:
快速版本:
real 0m2.132s
user 0m0.773s
sys 0m1.312s
朴素版:
real 2m36.480s
user 1m12.671s
sys 1m22.425s
最后,请注意,如果多个参与者同时尝试在序列中创建文件,这两种方法都容易受到竞争条件的影响。
【讨论】:
请注意,这段代码似乎有一些浮点/整数混淆,并且在我的文件名中添加了额外的句点(例如file-6.0.txt
而不是 file-6.txt
)。不过,我喜欢这个答案的原则。
感谢 @GiselleSerate,看起来 Python 3 处理整数除法的方式与 Python 2 不同。我已更新代码以使用 //
运算符而不是 /
,这似乎可以解决问题。 【参考方案5】:
我需要做类似的事情,但对于数据处理管道中的输出目录。我受到 Vorticity 的回答的启发,但添加了使用正则表达式来获取尾随数字。即使删除了中间编号的输出目录,此方法也会继续递增最后一个目录。它还添加了前导零,因此名称将按字母顺序排序(即宽度 3 给出 001 等)
def get_unique_dir(path, width=3):
# if it doesn't exist, create
if not os.path.isdir(path):
log.debug("Creating new directory - ".format(path))
os.makedirs(path)
return path
# if it's empty, use
if not os.listdir(path):
log.debug("Using empty directory - ".format(path))
return path
# otherwise, increment the highest number folder in the series
def get_trailing_number(search_text):
serch_obj = re.search(r"([0-9]+)$", search_text)
if not serch_obj:
return 0
else:
return int(serch_obj.group(1))
dirs = glob(path + "*")
num_list = sorted([get_trailing_number(d) for d in dirs])
highest_num = num_list[-1]
next_num = highest_num + 1
new_path = "0_1:0>2".format(path, next_num, width)
log.debug("Creating new incremented directory - ".format(new_path))
os.makedirs(new_path)
return new_path
get_unique_dir("output")
【讨论】:
【参考方案6】:您可以使用带计数器的 while 循环来检查具有名称和计数器值的文件是否存在,如果存在则继续操作 else 中断并生成文件。
我的一个项目就是这样完成的:`
from os import path
import os
i = 0
flnm = "Directory\\Filename" + str(i) + ".txt"
while path.exists(flnm) :
flnm = "Directory\\Filename" + str(i) + ".txt"
i += 1
f = open(flnm, "w") #do what you want to with that file...
f.write(str(var))
f.close() # make sure to close it.
`
这里的计数器 i 从 0 开始,while 循环每次检查文件是否存在,如果存在则继续,否则它会中断并创建一个文件,然后您可以自定义。还要确保关闭它,否则它会导致文件被打开,这可能会在删除它时导致问题。
我使用 path.exists() 检查文件是否存在。
不要这样做from os import *
,当我们使用 open() 方法时可能会导致问题,因为还有另一个 os.open() 方法,它会给出错误。 TypeError: Integer expected. (got str)
否则祝你新年快乐。
【讨论】:
【参考方案7】:另一个避免使用 while 循环的解决方案是使用 os.listdir()
函数,该函数返回一个包含在路径作为参数的目录中的所有文件和目录的列表。
回答问题中的例子,假设你工作的目录只包含从0开始索引的“sample_i.xlm”文件,你可以通过以下代码轻松获取新文件的下一个索引。
import os
new_index = len(os.listdir('path_to_file_containing_only_sample_i_files'))
new_file = open('path_to_file_containing_only_sample_i_files/sample_%s.xml' % new_index, 'w')
【讨论】:
虽然这不能很好地处理跳过的数字,但只要不担心,这是实现目标的一种非常简单的方法。【参考方案8】:def get_nonexistant_path(fname_path):
"""
Get the path to a filename which does not exist by incrementing path.
Examples
--------
>>> get_nonexistant_path('/etc/issue')
'/etc/issue-1'
>>> get_nonexistant_path('whatever/1337bla.py')
'whatever/1337bla.py'
"""
if not os.path.exists(fname_path):
return fname_path
filename, file_extension = os.path.splitext(fname_path)
i = 1
new_fname = "-".format(filename, i, file_extension)
while os.path.exists(new_fname):
i += 1
new_fname = "-".format(filename, i, file_extension)
return new_fname
在打开文件之前,调用
fname = get_nonexistant_path("sample.xml")
这将为您提供'sample.xml'
或 - 如果这已经存在 - 'sample-i.xml'
其中 i 是最小的正整数,因此该文件尚不存在。
我建议使用os.path.abspath("sample.xml")
。如果你有~
作为主目录,你可能需要先expand it。
请注意,如果您同时运行多个实例,则此简单代码可能会出现竞争条件。如果这可能是个问题,请检查this question。
【讨论】:
【参考方案9】:另一个使用递归的例子
import os
def checkFilePath(testString, extension, currentCount):
if os.path.exists(testString + str(currentCount) +extension):
return checkFilePath(testString, extension, currentCount+1)
else:
return testString + str(currentCount) +extension
用途:
checkFilePath("myfile", ".txt" , 0)
【讨论】:
【参考方案10】:尝试设置一个计数变量,然后递增嵌套在您写入文件的同一个循环内的该变量。在文件名中包含一个转义字符的计数循环,这样每个循环都会打+1,所以文件中的编号。
我刚刚完成的项目中的一些代码:
numberLoops = #some limit determined by the user
currentLoop = 1
while currentLoop < numberLoops:
currentLoop = currentLoop + 1
fileName = ("log%d_%d.txt" % (currentLoop, str(now())))
供参考:
from time import mktime, gmtime
def now():
return mktime(gmtime())
这可能与您的情况无关,但我正在运行该程序的多个实例并制作大量文件。希望这会有所帮助!
【讨论】:
Python 为此提供了 for 循环,它们比模拟它们的 while 循环阅读和理解要快得多。此外,%
运算符已被弃用。不过,没有反对意见,因为它完成了这项工作——它只是没有以首选的 Python 方式完成。
你的格式字符串有问题:你用%d
格式化一个字符串,这会引发异常。
感谢您的关注。应该是 %s,我匆忙地重新输入了这个,而不是从我的源代码中复制。谢谢!【参考方案11】:
例如,我将遍历sample[int].xml
并获取文件或目录未使用的下一个可用名称。
import os
i = 0
while os.path.exists("sample%s.xml" % i):
i += 1
fh = open("sample%s.xml" % i, "w")
....
最初应该给你 sample0.xml,然后是 sample1.xml,等等。
请注意,默认情况下,相对文件表示法与您运行代码的文件目录/文件夹相关。必要时使用绝对路径。使用os.getcwd()
读取您的当前目录,使用os.chdir(path_to_dir)
设置一个新的当前目录。
【讨论】:
请问这里什么是无用或不具建设性的?在不留下(建设性)评论的情况下投票对我来说似乎更不具建设性。isfile()
不正确:目录将匹配。你想要exists()
,但这是@Eiyrioü von Kauyf 的答案。此外,相对路径并不完全“相对于运行代码的目录”。相反,它们通常相对于“当前目录”(默认情况下是运行代码的目录)。例如,可以在程序中更改当前目录。
os.path.isfile() 匹配目录的事实对我来说是新的(并且不像你在 Python 3.3/win 上为我描述的那样),这不是为什么有 os .path.isdir() 可以区分两者吗?关于我帖子中对相对路径表示法的评论,Oliver Ven Quilnet 和我的示例都没有明确更改 当前目录,我想我简要指出它以明确 给定上下文.
你说得对,我应该更清楚。我的意思是isfile()
将使您的循环在名称与目录匹配时退出,然后您的代码会尝试以写入模式打开目录,但会以IOError
失败。这就是为什么isfile()
不是正确的测试,应该用@Eiyrioü von Kauyf 的exists()
代替。至于相对路径,我真的认为当前的“相对文件表示法总是与您运行代码的文件目录/文件夹相关”具有误导性(因为“总是”)。
@EOL:说得好,老实说,我不知道同一目录中的文件和文件夹之间的相同名称在 Windows 下是非法的;感谢您指出了这一点。我同意你的观点,关于相对路径的评论听起来确实有误导性,现在听起来应该更清楚了。【参考方案12】:
有两种方法:
-
检查旧文件是否存在,如果存在,请尝试下一个文件名 +1
在某处保存状态数据
一个简单的方法是:
import os.path as pth
filename = "myfile"
filenum = 1
while (pth.exists(pth.abspath(filename+str(filenum)+".py")):
filenum+=1
my_next_file = open(filename+str(filenum)+".py",'w')
作为一种设计,while True
会减慢速度,对代码可读性来说并不是一件好事
已编辑:@EOL 贡献/想法
所以我认为没有 .format 乍一看更具可读性 - 但使用 .format 更适合一般性和约定。
import os.path as pth
filename = "myfile"
filenum = 1
while (pth.exists(pth.abspath(filename+str(filenum)+".py")):
filenum+=1
my_next_file = open(".py".format(filename, filenum),'w')
# or
my_next_file = open(filename + ".py".format(filenum),'w')
而且您不必使用 abspath - 如果您愿意,可以使用相对路径,我有时更喜欢 abs 路径,因为它有助于规范传递的路径:)。
import os.path as pth
filename = "myfile"
filenum = 1
while (pth.exists(filename+str(filenum)+".py"):
filenum+=1
##removed for conciseness
【讨论】:
format()
方法比字符串连接更易读,这里。我认为while循环很好,在这里。在另一个话题上,为什么要使用abspath()
?
格式更易读,但接下来他将不得不查看字符串格式;乍一看,恕我直言,这更容易理解。和 abspath 因为我忽略了符号链接:/ .... 这可能会导致令人困惑的错误
虽然我理解你的观点,但我相信即使是初学者也应该看到 Pythonic 的例子,以便他们养成良好的习惯。 format()
的行为真的很容易理解甚至猜测:".py".format(filename, filenum)
。它甚至比这里介绍的算法更简单。 :)
@EOL 你怎么想 ;) 我得到你的同意了吗【参考方案13】:
如果不将状态数据存储在额外的文件中,对于此处介绍的问题,更快的解决方案是执行以下操作:
from glob import glob
import os
files = glob("somedir/sample*.xml")
files = files.sorted()
cur_num = int(os.path.basename(files[-1])[6:-4])
cur_num += 1
fh = open("somedir/sample%s.xml" % cur_num, 'w')
rs = [blockresult]
fh.writelines(rs)
fh.close()
即使一些编号较低的文件消失了,这也会继续增加。
我喜欢的另一个解决方案(由 Eiyrioü 指出)是保留一个包含您最近号码的临时文件:
temp_fh = open('somedir/curr_num.txt', 'r')
curr_num = int(temp_fh.readline().strip())
curr_num += 1
fh = open("somedir/sample%s.xml" % cur_num, 'w')
rs = [blockresult]
fh.writelines(rs)
fh.close()
【讨论】:
您的cur_num
计算仅适用于1 位数字,不够通用。以上是关于如何在 Python 中创建递增的文件名?的主要内容,如果未能解决你的问题,请参考以下文章