Buenas, estoy haciendo un script que recorre un directorio con muchos subniveles y archivos, y a estos archivos los tiene que copiar a otro directorio (afuera del que ya estoy recorriendo), pero todos en el mismo nivel. Es decir, tengo algo así para recorrer:
DirA
|
|-DirB
|-readme.txt
|-archivo.txt
|-DirC
|-readme.txt
|-otro-archivo.txt
...
Y genero un nuevo directorio así
NuevoDir
|-readme.txt
|-readme.txt-0
|-archivo.txt
|-otro-archivo.txt
...
El problema obvio es que hay nombres que se pueden repetir y tengo que armar un nuevo nombre (Esto puede ocurrir un numero de veces indeterminado). Recorro el directorio con os.walk, así que en principio no tengo el listado completo de archivos (y de hecho, cada tanto cambia).
Simplificando un poco, lo resolví con este código:
sufijo = 0
nombre_archivo_original = nombre_archivo
while os.path.exists(nombre_archivo):
nombre_archivo = nombre_archivo_original + "-" + str(sufijo)
sufijo += 1
Cuando sale de ese bucle, tengo un nombre "valido" (o sea, funciona), pero me gustaría saber si existe una forma mas "pythonica" de generar ese nuevo nombre.
Si bien no mencionados, algunas caracteristicas deseables del script son las siguientes:
Se comienza con un setup basico para las pruebas
import timeit
REPETICION = 10000 # Establece el numero de veces que se ejecutara cada test
La forma mas sencilla de discriminante es utilizando un contador. Sin mantener estado, solo sirve para el script en ejecucion y no es reutilizable (En principio).
timeit.timeit('c = 0; c += 1', number=REPETICION)
0.0005691051483154297
La forma mas sencilla, se prueba crear el archivo con el nombre original. Si falla, se reintenta con '0', si vuelve a fallar se reintenta con '1', y asi. El consumo de memoria es minimo (un integer), pero para n archivos con el mismo nombre, hay que reintentar la creacion del archivo n - 1 veces. A esto le decimos que no guarda estado. Una mejora sencilla seria que el contador sea global. Se pierde asi la referencia de cuantas veces se repite cada nombre de archivo, pero se evitan los reintentos innecesarios.
timeit.timeit('uuid.uuid1().hex', 'import uuid', number=REPETICION)
0.28641295433044434
Es acertado y cumple con lo pedido. No es demasiado eficiente en cuanto al tiempo de generacion, pero es entendible respecto al procesamiento que realiza.
Propuesta de Miguel: Uso del modulo mkstemp
. Para los fines de test, es conveniente crear una carpeta de prueba porque genera mucha cantidad de archivos.
mkdir 'delete-after-test'
mkdir: cannot create directory ‘delete-after-test’: File exists
timeit.timeit("(fh, name) = tempfile.mkstemp(dir='./delete-after-test/', prefix='readme-',suffix='.txt');os.close(fh);", "import os; import tempfile", number=REPETICION)
0.464108943939209
Crea el archivo vacio, y en este caso el archivo tiene que ser borrado para que el otro sea copiado posteriormente desde su origen. Ademas, genera un IO cada vez que se ejecuta, y como hay que hacerlo por cada archivo a copiar, se duplican la cantidad de escrituras al Disco Rigido. Parece una buena idea para resolver el problema de una generacion eventual de nombre, pero no para un copiado masivo.
Propuesta de German: Usar el path completo para construir el nombre de archivo.
timeit.timeit("'-'.join(path_completo.split('/'))", "path_completo = 'DirA/DirB/DirC/readme.txt'", number=REPETICION)
0.004395008087158203
La solucion es interesante, cumple con lo que se necesita y gracias a que se usa una restriccion del FS asegura la unicidad de los nombres.
Propuesta de Marian: Armar un dic con los sufijos. Si no entendi mal, es similar a la propuesta del contandor, pero el script va manteniendo en un diccionario cada uno de los nombres de archivo repetidos, y para cada uno cuenta la repeticion, que a su vez se usa como sufijo.
timeit.timeit("f_dic['key'] = f_dic['key'] + 1 if f_dic.has_key('key') else 0; sufijo = f_dic['key'];", "f_dic = {}", number=REPETICION)
0.002296924591064453
Permite mantener el estado, cosa que no ocurria con el contador. Para ello necesita una estructura adicional (el diccionario). Si la cantidad de archivos no es masiva, es un costo que se puede asumir, siempre que interese saber cuantas veces se repite cada nombre de archivo.
Se utilizo la lib timeit_plot para graficar los tiempos de los diferentes metodos.
import timeit_plot as tp
from matplotlib import pyplot as plt
%matplotlib inline
functions = ['c = 0; c += 1',
'uuid.uuid1().hex',
"(fh, name) = tempfile.mkstemp(dir='./delete-after-test/', prefix='readme-',suffix='.txt');os.close(fh);",
"'-'.join(path_completo.split('/'))",
"f_dic['key'] = f_dic['key'] + 1 if f_dic.has_key('key') else 0; sufijo = f_dic['key'];"]
setup_code = ['pass',
'import uuid;',
"import os; import tempfile;",
"path_completo = 'DirA/DirB/DirC/readme.txt';",
"f_dic = {}"]
data = tp.timeit_compare(functions, range(10,101,10), setups=setup_code, number=REPETICION)
testing c = 0; c += 1... testing uuid.uuid1().hex... testing (fh, name) = tempfile.mkstemp(dir='./delete-after-test/', prefix='readme-',suffix='.txt');os.close(fh);... testing '-'.join(path_completo.split('/'))... testing f_dic['key'] = f_dic['key'] + 1 if f_dic.has_key('key') else 0; sufijo = f_dic['key'];...
tp.timeit_plot2D(data, 'list length', 'Comparacion')
functions = ['c = 0; c += 1',
"'-'.join(path_completo.split('/'))",
"f_dic['key'] = f_dic['key'] + 1 if f_dic.has_key('key') else 0; sufijo = f_dic['key'];"]
setup_code = ['pass',
"path_completo = 'DirA/DirB/DirC/readme.txt';",
"f_dic = {}"]
data = tp.timeit_compare(functions, range(10,101,10), setups=setup_code, number=REPETICION)
testing c = 0; c += 1... testing '-'.join(path_completo.split('/'))... testing f_dic['key'] = f_dic['key'] + 1 if f_dic.has_key('key') else 0; sufijo = f_dic['key'];...
tp.timeit_plot2D(data, 'list lengt', 'Comparacion')