É esta a melhor maneira de obter a versão única de nome de arquivo w / Python?
Pergunta
Ainda 'mergulho em' para Python, e quero ter certeza que eu não estou esquecendo algo. Eu escrevi um script que extrai arquivos de vários arquivos zip, e salva os arquivos extraídos juntos em um diretório. Para evitar nomes de arquivos duplicados de ser sobre-escrito, eu escrevi essa pequena função - e eu só estou querendo saber se existe uma maneira melhor de fazer isso? Obrigado!
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
Eu realmente do requerem os arquivos para estar em um único diretório, e numeração de duplicatas é definitivamente aceitável no meu caso, então eu não estou procurando um método mais robusto (tho' Suponho que qualquer ponteiros são bem-vindos), mas apenas para se certificar de que o que isso realiza está sendo feito da maneira certa.
Solução
Uma questão é que existe uma condição de corrida em seu código acima, uma vez que existe uma lacuna entre o teste de existência, e criar o arquivo. Pode haver implicações de segurança para este (Pense em alguém maliciosamente inserir um link simbólico para um arquivo confidencial que não seria capaz de substituir, mas o seu programa em execução com um privilégio mais alto possível) Ataques como estes são porque coisas como os.tempnam ( ) estão obsoletos.
Para contornar o problema, a melhor abordagem é realmente tentar criar o arquivo de tal forma que você vai ter uma exceção se ele falhar, e em caso de sucesso, retornar o objeto de arquivo, na verdade, aberto. Isso pode ser feito com o nível mais baixo os.open funções, passando as bandeiras os.O_CREAT e os.O_EXCL. Depois de aberto, o retorno do arquivo real (e, opcionalmente, filename) você cria. Por exemplo, aqui está o código modificado para usar essa abordagem (retornando um (arquivo, filename) tuple):
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
[Edit] Na verdade, uma maneira melhor, que vai lidar com as questões acima para você, é, provavelmente, usar o módulo tempfile, embora você pode perder algum controle sobre a nomeação. Aqui está um exemplo de usá-lo (mantendo uma interface similar):
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
A única desvantagem dessa abordagem é que você sempre vai ter um nome de arquivo com alguns caracteres aleatórios na mesma, como não há nenhuma tentativa de criar um arquivo modificado (/home/some_dir/foo.txt) em primeiro lugar. Você também pode querer olhar para tempfile.TemporaryFile e NamedTemporaryFile, que vai fazer o exposto, e também automaticamente excluir do disco quando fechado.
Outras dicas
Sim, esta é uma boa estratégia para nomes de arquivos legíveis, mas únicos.
Uma mudança importante : Você deve substituir os.path.isfile
com os.path.lexists
! Como está escrito no momento, se há um diretório chamado /foo/bar.baz, o programa irá tentar substituir que com o novo arquivo (que não vai funcionar) ... desde que isfile
somente verifica arquivos e não diretórios . cheques lexists
para diretórios, links simbólicos, etc ... basicamente, se há alguma razão que nome de arquivo não pôde ser criado.
EDIT:. @ Brian deu uma resposta melhor, o que é mais seguro e robusto em termos de condições de corrida
Duas pequenas mudanças ...
base_name, ext = os.path.splitext(file_name)
Você tem dois resultados com significado distinto, dar-lhes nomes distintos.
file_name = "%s_%d%s" % (base_name, str(counter), ext)
Não é mais rápido ou significativamente menor. Mas, quando você quiser mudar o seu padrão de nome de arquivo, o padrão está em um lugar, e um pouco mais fácil de trabalhar.
Se você quiser nomes legíveis esta parece ser uma boa solução.
Existem rotinas para retornar nomes de arquivo exclusivo para, por exemplo. arquivos temporários, mas eles produzem longos nomes aleatórios olhando.
Se você não se preocupam com a legibilidade, uuid.uuid4 () é seu amigo.
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)
Como cerca
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 () retorna o tempo atual em milissegundos. combinado com nome de arquivo original, que é bastante singular, mesmo em casos de vários segmentos complexos.