Это лучший способ получить уникальную версию имени файла с Python? - PullRequest
14 голосов
/ 08 октября 2008

Все еще погружаюсь в 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

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

Ответы [ 6 ]

22 голосов
/ 08 октября 2008

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

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

6 голосов
/ 08 октября 2008

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

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

РЕДАКТИРОВАТЬ: @Brian дал лучший ответ, который является более безопасным и надежным с точки зрения условий гонки.

2 голосов
/ 08 октября 2008

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

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

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

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

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

1 голос
/ 09 октября 2008

если вас не волнует удобочитаемость, 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)
1 голос
/ 08 октября 2008

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

0 голосов
/ 27 марта 2009

Как насчет

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...