recorrer - rutas de archivos en python
puntos de referencia: ¿Python tiene una forma más rápida de recorrer una carpeta de red? (2)
Necesito recorrer una carpeta con aproximadamente diez mil archivos. Mi viejo vbscript es muy lento en el manejo de esto. Desde que comencé a usar Ruby y Python desde entonces, hice un punto de referencia entre los tres lenguajes de scripting para ver cuál sería la mejor opción para este trabajo.
Los resultados de las pruebas a continuación en un subconjunto de 4500 archivos en una red compartida son
Python: 106 seconds
Ruby: 5 seconds
Vbscript: 124 seconds
Que Vbscript sería más lento no fue una sorpresa, pero no puedo explicar la diferencia entre Ruby y Python. ¿Mi prueba para Python no es óptima? ¿Hay una manera más rápida de hacer esto en Python?
La prueba para thumbs.db es solo para la prueba, en realidad hay más pruebas para hacer.
Necesitaba algo que verifica cada archivo en la ruta y no produce demasiada salida para no perturbar el tiempo. Los resultados son un poco diferentes en cada ejecución pero no mucho.
#python2.7.0
import os
def recurse(path):
for (path, dirs, files) in os.walk(path):
for file in files:
if file.lower() == "thumbs.db":
print (path+''/''+file)
if __name__ == ''__main__'':
import timeit
path = ''//server/share/folder/''
print(timeit.timeit(''recurse("''+path+''")'', setup="from __main__ import recurse", number=1))
''vbscript5.7
set oFso = CreateObject("Scripting.FileSystemObject")
const path = "//server/share/folder"
start = Timer
myLCfilename="thumbs.db"
sub recurse(folder)
for each file in folder.Files
if lCase(file.name) = myLCfilename then
wscript.echo file
end if
next
for each subfolder in folder.SubFolders
call Recurse(subfolder)
next
end Sub
set folder = oFso.getFolder(path)
recurse(folder)
wscript.echo Timer-start
#ruby1.9.3
require ''benchmark''
def recursive(path, bench)
bench.report(path) do
Dir["#{path}/**/**"].each{|file| puts file if File.basename(file).downcase == "thumbs.db"}
end
end
path = ''//server/share/folder/''
Benchmark.bm {|bench| recursive(path, bench)}
EDITAR: como sospechaba que la impresión causó un retraso, probé las secuencias de comandos con la impresión de todos los 4500 archivos y tampoco imprimí ninguna, la diferencia permanece, R: 5 P: 107 en el primer caso y R: 4.5 P: 107 en el segundo
EDIT2: basado en las respuestas y comentarios aquí una versión de Python que en algunos casos podría ejecutarse más rápido omitiendo carpetas
import os
def recurse(path):
for (path, dirs, files) in os.walk(path):
for file in files:
if file.lower() == "thumbs.db":
print (path+''/''+file)
def recurse2(path):
for (path, dirs, files) in os.walk(path):
for dir in dirs:
if dir in (''comics''):
dirs.remove(dir)
for file in files:
if file.lower() == "thumbs.db":
print (path+''/''+file)
if __name__ == ''__main__'':
import timeit
path = ''f:/''
print(timeit.timeit(''recurse("''+path+''")'', setup="from __main__ import recurse", number=1))
#6.20102692
print(timeit.timeit(''recurse2("''+path+''")'', setup="from __main__ import recurse2", number=1))
#2.73848228
#ruby 5.7
Configuré la estructura del directorio con lo siguiente de forma local:
for i in $(seq 1 4500); do
if [[ $i -lt 100 ]]; then
dir="$(for j in $(seq 1 $i); do echo -n $i/;done)"
mkdir -p "$dir"
touch ${dir}$i
else
touch $i
fi
done
Esto crea 99 archivos con rutas que tienen 1-99 niveles de profundidad y 4401 archivos en la raíz de la estructura del directorio.
Usé el siguiente script de ruby:
#!/usr/bin/env ruby
require ''benchmark''
def recursive(path, bench)
bench.report(path) do
Dir["#{path}/**/**"]
end
end
path = ''files''
Benchmark.bm {|bench| recursive(path, bench)}
Obtuve el siguiente resultado:
user system total real
files/ 0.030000 0.090000 0.120000 ( 0.108562)
Utilizo el siguiente script de Python usando os.walk:
#!/usr/bin/env python
import os
import timeit
def path_recurse(path):
for (path, dirs, files) in os.walk(path):
for folder in dirs:
yield ''{}/{}''.format(path, folder)
for filename in files:
yield ''{}/{}''.format(path, filename)
if __name__ == ''__main__'':
path = ''files''
print(timeit.timeit(''[i for i in path_recurse("''+path+''")]'', setup="from __main__ import path_recurse", number=1))
Obtuve el siguiente resultado:
0.250478029251
Por lo tanto, parece que Ruby todavía está funcionando mejor. Sería interesante ver cómo funciona esto en su conjunto de archivos en el recurso compartido de red.
Probablemente también sea interesante ver este script ejecutarse en python3 y con jython y tal vez incluso con pypy.
La implementación de Ruby para Dir
está en C (el archivo dir.c
, según esta documentación ). Sin embargo, el equivalente de Python se implementa en Python .
No es sorprendente que Python sea menos eficiente que C, pero el enfoque utilizado en Python le da un poco más de flexibilidad, por ejemplo, puede omitir subárboles enteros nombrados, por ejemplo, ''.svn''
, ''.git''
, ''.hg''
al atravesar un jerarquía de directorio.
La mayoría de las veces, la implementación de Python es lo suficientemente rápida.
Actualización: La omisión de archivos / subdirectorios no afecta en absoluto la velocidad de cruce, pero el tiempo total que lleva procesar un árbol de directorios puede reducirse sin duda porque evita tener que atravesar subárboles potencialmente grandes del árbol principal. El tiempo ahorrado es, por supuesto, proporcional a la cantidad que omite. En su caso, que parece carpetas de imágenes, es poco probable que ahorre mucho tiempo (a menos que las imágenes estuvieran bajo control de revisión, cuando omitir subárboles propiedad del sistema de control de revisiones podría tener algún impacto).
Actualización adicional: omitir carpetas se hace cambiando el valor de los dirs
en su lugar:
for root, dirs, files in os.walk(path):
for skip in (''.hg'', ''.git'', ''.svn'', ''.bzr''):
if skip in dirs:
dirs.remove(skip)
# Now process other stuff at this level, i.e.
# in directory "root". The skipped folders
# won''t be recursed into.