Это лучший способ получить уникальную версию имени файла с Python?

Все еще «ныряю» в Python и хочу убедиться, что что-то не упускаю из виду. Я написал сценарий, который извлекает файлы из нескольких 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

Мне действительно делать требуется, чтобы файлы находились в одном каталоге, и нумерация дубликатов определенно приемлема в моем случае, поэтому я не ищу более надежный метод (хотя, я полагаю, любые указатели приветствуются), но просто чтобы убедиться что то, что этим достигается, делается правильно.

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
15
0
7 989
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Если вам нужны удобочитаемые имена, это хорошее решение. Существуют процедуры для возврата уникальных имен файлов, например. временные файлы, но они производят длинные случайные имена.

Два небольших изменения ...

base_name, ext = os.path.splitext(file_name) 

Вы получите два результата с разными значениями, дайте им разные имена.

file_name = "%s_%d%s" % (base_name, str(counter), ext)

Это не быстрее и не короче. Но когда вы хотите изменить шаблон имени файла, шаблон находится в одном месте, и с ним немного легче работать.

Да, это хорошая стратегия для удобочитаемых, но уникальных имен файлов.

Одно важное изменение: Вам следует заменить os.path.isfile на os.path.lexists! Как написано прямо сейчас, если есть каталог с именем /foo/bar.baz, ваша программа попытается перезаписать его новым файлом (который не будет работать) ... поскольку isfile проверяет только файлы, а не каталоги . lexists проверяет каталоги, символические ссылки и т.д ... в основном, если есть какая-либо причина, по которой имя файла не может быть создано.

Обновлено: @Brian дал лучший ответ, который более безопасен и надежен с точки зрения условий гонки.

Ответ принят как подходящий

Одна из проблем заключается в том, что в приведенном выше коде присутствует состояние гонки, поскольку существует разрыв между проверкой существования и созданием файла. Это может иметь последствия для безопасности (подумайте о том, что кто-то злонамеренно вставляет символическую ссылку на конфиденциальный файл, который он не сможет перезаписать, но ваша программа, работающая с более высокими привилегиями, может). Такие атаки, почему такие вещи, как 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, которые сделают то же самое, а также автоматически удалят с диска при закрытии.

Да, это Правильный_Путь для этого. Хотел бы я изменить себя и поставить ваш ответ вверху!

Dan Lenski 08.10.2008 23:33

Небольшая опечатка: это должен быть os.O_RDWR, а не os.O_RDRW

tremby 21.11.2012 09:30

если вас не волнует удобочитаемость, 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)

Как насчет

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 () возвращает текущее время в миллисекундах. в сочетании с исходным именем файла он довольно уникален даже в сложных многопоточных случаях.

Другие вопросы по теме