Это лучший способ получить уникальную версию имени файла w/Python?
"Дайвинг в" на Python, и я хочу убедиться, что я ничего не забываю. Я написал script, который извлекает файлы из нескольких zip файлов и сохраняет извлеченные файлы вместе в одном каталоге. Чтобы предотвратить дублирование повторяющихся имен файлов, я написал эту небольшую функцию - и мне просто интересно, есть ли лучший способ сделать это?
Спасибо!
def unique_filename(file_name):
counter = 1
file_name_parts = os.path.splitext(file_name) # returns ('/path/file', '.ext')
while os.path.isfile(file_name):
file_name = file_name_parts[0] + '_' + str(counter) + file_name_parts[1]
counter += 1
return file_name
Я действительно требую, чтобы файлы были в одном каталоге, а нумерация дубликатов в моем случае, безусловно, приемлема, поэтому я не ищу более надежный метод ("Я полагаю, что любые указатели приветствуются), но просто чтобы убедиться в том, что все это делается правильно.
Ответы
Ответ 1
Одна из проблем заключается в том, что в вашем предыдущем коде есть условие гонки, так как существует разрыв между тестированием на существование и созданием файла. Могут быть последствия для безопасности для этого (подумайте о том, что кто-то злонамеренно вставляет символическую ссылку в чувствительный файл, который они не смогут перезаписать, но ваша программа работает с более высокой привилегией). Атаки, подобные этим, - вот почему такие вещи, как os.tempnam( ) устарели.
Чтобы обойти это, лучше всего попытаться создать файл таким образом, чтобы получить исключение, если оно не удается, и при успешном завершении вернет фактически открытый файловый объект. Это можно сделать с помощью функций os.open более низкого уровня, передав флаги os.O_CREAT и os.O_EXCL. После открытия верните фактический файл (и, возможно, имя файла), который вы создаете. Например, здесь ваш код изменился, чтобы использовать этот подход (вернув кортеж (файл, имя файла)):
def unique_file(file_name):
counter = 1
file_name_parts = os.path.splitext(file_name) # returns ('/path/file', '.ext')
while 1:
try:
fd = os.open(file_name, os.O_CREAT | os.O_EXCL | os.O_RDRW)
return os.fdopen(fd), file_name
except OSError:
pass
file_name = file_name_parts[0] + '_' + str(counter) + file_name_parts[1]
counter += 1
[Изменить] На самом деле, лучший способ, который будет обрабатывать вышеупомянутые проблемы для вас, вероятно, использовать модуль tempfile, хотя вы можете потерять некоторый контроль над именованием. Вот пример его использования (сохранение аналогичного интерфейса):
def unique_file(file_name):
dirname, filename = os.path.split(file_name)
prefix, suffix = os.path.splitext(filename)
fd, filename = tempfile.mkstemp(suffix, prefix+"_", dirname)
return os.fdopen(fd), filename
>>> f, filename=unique_file('/home/some_dir/foo.txt')
>>> print filename
/home/some_dir/foo_z8f_2Z.txt
Единственным недостатком такого подхода является то, что вы всегда получите имя файла с некоторыми случайными символами в нем, так как вначале не пытайтесь создать немодифицированный файл (/home/some_dir/foo.txt).
Вы также можете посмотреть tempfile.TemporaryFile и NamedTemporaryFile, который будет делать это, а также автоматически удалять с диска при закрытии.
Ответ 2
Да, это хорошая стратегия для читаемых, но уникальных имен файлов.
Одно важное изменение. Вы должны заменить os.path.isfile
на os.path.lexists
! Поскольку это написано прямо сейчас, если есть каталог с именем /foo/bar.baz, ваша программа попытается перезаписать это с новым файлом (который не будет работать)... поскольку isfile
проверяет только файлы и не справочники. lexists
проверяет каталоги, символические ссылки и т.д.... в основном, если есть какая-то причина, по которой имя файла не может быть создано.
EDIT: @Brian дал лучший ответ, более безопасный и надежный с точки зрения условий гонки.
Ответ 3
Два небольших изменения...
base_name, ext = os.path.splitext(file_name)
Вы получаете два результата с различным значением, даете им разные имена.
file_name = "%s_%d%s" % (base_name, str(counter), ext)
Это не быстрее или значительно короче. Но, когда вы хотите изменить шаблон имени файла, шаблон находится на одном месте и с ним немного легче работать.
Ответ 4
Если вам нужны читаемые имена, это выглядит как хорошее решение.
Существуют подпрограммы для возврата уникальных имен файлов, например. temp, но они производят длинные случайные имена.
Ответ 5
если вам не нравится читаемость, uuid.uuid4() - ваш друг.
import uuid
def unique_filename(prefix=None, suffix=None):
fn = []
if prefix: fn.extend([prefix, '-'])
fn.append(str(uuid.uuid4()))
if suffix: fn.extend(['.', suffix.lstrip('.')])
return ''.join(fn)
Ответ 6
Как насчет
def ensure_unique_filename(orig_file_path):
from time import time
import os
if os.path.lexists(orig_file_path):
name, ext = os.path.splitext(orig_file_path)
orig_file_path = name + str(time()).replace('.', '') + ext
return orig_file_path
time() возвращает текущее время в миллисекундах. в сочетании с оригинальным именем файла, он довольно уникален даже в сложных многопоточных случаях.