Подтвердить что ты не робот

Это лучший способ получить уникальную версию имени файла 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

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

4b9b3361

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