¿Hay alguna forma de generar eficientemente cada archivo en un directorio que contenga millones de archivos?

Soy consciente de os.listdir , pero en la medida en que puedo recostackrlo, todos los nombres de los archivos de un directorio entran en la memoria y luego devuelven la lista. Lo que quiero es una forma de generar un nombre de archivo, trabajar en él y luego generar el siguiente sin leerlos todos en la memoria.

¿Hay alguna manera de hacer esto? Me preocupa el caso en el que los nombres de los archivos cambian, se agregan nuevos archivos y los archivos se eliminan con este método. Algunos iteradores le impiden modificar la colección durante la iteración, esencialmente al tomar una instantánea del estado de la colección al principio y comparar ese estado en cada operación de move . Si hay un iterador capaz de generar nombres de archivo desde una ruta, ¿genera un error si hay cambios en el sistema de archivos (agregar, eliminar, renombrar archivos dentro del directorio iterado) que modifican la colección?

Potencialmente, podría haber algunos casos que podrían hacer que el iterador falle, y todo depende de cómo el iterador mantenga el estado. Usando el ejemplo de S.Lotts:

 filea.txt fileb.txt filec.txt 

El iterador produce filea.txt . Durante el processing , filea.txt cambia de nombre a filey.txt y fileb.txt cambia de nombre a filez.txt . Cuando el iterador intenta obtener el siguiente archivo, si usara el nombre de archivo filea.txt para encontrar su posición actual para encontrar el siguiente archivo y filea.txt no está allí, ¿qué pasaría? Es posible que no pueda recuperar su posición en la colección. De manera similar, si el iterador fuera a buscar fileb.txt al filea.txt , podría buscar la posición de fileb.txt , fallar y producir un error.

Si el iterador en su lugar pudiera mantener de alguna manera un dir.get_file(0) , entonces el estado posicional no se vería afectado, pero algunos archivos podrían perderse, ya que sus índices podrían moverse a un índice “detrás” del iterador.

Por supuesto, todo esto es teórico, ya que no parece haber una forma incorporada (python) de iterar sobre los archivos en un directorio. Sin embargo, a continuación hay algunas respuestas geniales que solucionan el problema mediante el uso de colas y notificaciones.

Editar:

El sistema operativo de preocupación es Redhat. Mi caso de uso es este:

El proceso A está continuamente escribiendo archivos en una ubicación de almacenamiento. El proceso B (el que estoy escribiendo), se repetirá sobre estos archivos, se procesará según el nombre del archivo y se moverán los archivos a otra ubicación.

Editar:

Definición de válido:

Adjetivo 1. Bien fundamentado o justificable, pertinente.

(Lo siento S. Lott, no pude resistirme).

He editado el párrafo en cuestión arriba.

tl; dr : a partir de Python 3.5 (actualmente en versión beta) solo use os.scandir

Como he escrito anteriormente, dado que “iglob” es solo una fachada para un iterador real, tendrá que llamar a las funciones del sistema de bajo nivel para obtener una a la vez como desee. Por fortuna, eso es factible desde Python. Si no ha dicho si está en un sistema Posix (Linux / mac OS X / otro Unix) o Windows. En el último caso, debe verificar si win32api tiene alguna llamada para leer “la siguiente entrada de un directorio” o cómo proceder de otra manera.

En el primer caso, puede proceder a llamar a las funciones libc directamente a través de ctypes y obtener una entrada de directorio de archivos (incluida la información de nombres) una vez.

La documentación sobre las funciones de C está aquí: http://www.gnu.org/s/libc/manual/html_node/Opening-a-Directory.html#Opening-a-Directory

http://www.gnu.org/s/libc/manual/html_node/Reading_002fClosing-Directory.html#Reading_002fClosing-Directory

Desafortunadamente, la estructura C de “dirent64” se determina en el momento de la comstackción de C para cada sistema; había calculado que en mi sistema, y ​​en la mayoría, sería como si lo pusiera en Python en el siguiente fragmento de código, pero es posible que desee checj su “dirent.h” y otros campos que incluye en / usr / includes.

Aquí está el fragmento de código que usa ctypes y libC que he reunido para que pueda obtener cada nombre de archivo y realizar acciones en él. Tenga en cuenta que ctypes automáticamente le da una cadena de Python cuando hace str (…) en la matriz de caracteres definida en la estructura. (Estoy usando la statement de impresión, que implícitamente llama str de Python)

 from ctypes import * libc = cdll.LoadLibrary( "libc.so.6") dir_ = c_voidp( libc.opendir("/home/jsbueno")) class Dirent(Structure): _fields_ = [("d_ino", c_voidp), ("off_t", c_int64), ("d_reclen", c_ushort), ("d_type", c_ubyte), ("d_name", c_char * 2048) ] while True: p = libc.readdir64(dir_) if not p: break entry = Dirent.from_address( p) print entry.d_name 

actualización : Python 3.5 ahora está en versión beta – y en esta versión, la nueva función de llamada os.scandir está disponible como materialización de PEP 471 (“un iterador de directorio mejor y más rápido”) que hace exactamente lo que se solicita aquí, además de mucho otras optimizaciones que pueden ofrecer hasta 9 veces el aumento de velocidad sobre os.listdir en la lista de directorios grandes bajo Windows (aumento de 2-3 veces en los sistemas Posix).

El módulo global Python a partir de 2.5 tiene un método iglob que devuelve un iterador. Un iterador es exactamente para no almacenar grandes valores en la memoria.

 glob.iglob(pathname) Return an iterator which yields the same values as glob() without actually storing them all simultaneously. 

Por ejemplo:

 import glob for eachfile in glob.iglob('*'): # act upon eachfile 

Ya que está usando Linux, es posible que desee ver pyinotify . Le permitiría escribir una secuencia de comandos de Python que supervise un directorio en busca de cambios en el sistema de archivos, como la creación, modificación o eliminación de archivos.

Cada vez que se produce un evento de este tipo en el sistema de archivos, puede organizar que el script de Python llame a una función. Esto sería más o menos parecido a producir cada nombre de archivo una vez, al mismo tiempo que puede reactjsr a modificaciones y eliminaciones.

Parece que ya tienes un millón de archivos en un directorio. En este caso, si tuviera que mover todos esos archivos a un nuevo directorio supervisado por pyinotify, los eventos del sistema de archivos generados por la creación de nuevos archivos darán los nombres de archivo como se desee.

Lo que quiero es una forma de generar un nombre de archivo, trabajar en él y luego generar el siguiente sin leerlos todos en la memoria.

Ningún método revelará un nombre de archivo que “haya cambiado”. Ni siquiera está claro a qué se refiere con este “cambio de nombre de archivo, se agregan nuevos archivos y se eliminan archivos”? ¿Cuál es tu caso de uso?

Digamos que tienes tres archivos: aa , bb , cc .

Tu “iterador” mágico comienza con aa . Usted lo procesa.

El “iterador” mágico se mueve a bb . Lo estás procesando.

Mientras tanto, aa se copia en a1.a1 , aa se elimina. ¿Ahora que? ¿Qué hace tu iterador mágico con estos? Ya ha pasado aa . Como a1.a1 es anterior a bb , nunca lo verá. ¿Qué se supone que sucederá con “cambio de nombre de archivo, se agregan nuevos archivos y se eliminan archivos”?

El mágico “iterador” se mueve a cc . ¿Qué se suponía que iba a pasar con los otros archivos? ¿Y cómo se suponía que ibas a enterarte de la eliminación?


El proceso A está continuamente escribiendo archivos en una ubicación de almacenamiento. El proceso B (el que estoy escribiendo), se repetirá sobre estos archivos, se procesará según el nombre del archivo y se moverán los archivos a otra ubicación.

No utilice el sistema de archivos desnudo para la coordinación.

Use una cola.

El proceso A escribe los archivos y pone en cola el elemento de agregar / cambiar / eliminar en una cola.

El proceso B lee el memento de la cola y luego realiza el procesamiento de seguimiento en el archivo nombrado en el memento.

La publicación de @jsbueno es realmente útil, pero sigue siendo un poco lenta en los discos lentos, ya que libc readdir () solo prepara 32K de entradas de disco a la vez. No soy un experto en hacer llamadas al sistema directamente en Python, pero describí cómo escribir código en C que listará un directorio con millones de archivos, en una publicación de blog en: http://www.olark.com/spw/ 2011/08 / you-can-list-a-directory-with-8-million-files-but-not-with-ls / .

El caso ideal sería llamar a getdents () directamente en python ( http://www.kernel.org/doc/man-pages/online/pages/man2/getdents.2.html ) para que pueda especificar un tamaño de búfer de lectura al cargar entradas de directorio desde el disco.

En lugar de llamar a readdir () que, por lo que puedo decir, tiene un tamaño de búfer definido en el momento de la comstackción.

Creo que lo que está pidiendo es imposible debido a la naturaleza del archivo IO. Una vez que python ha recuperado la lista de un directorio, no puede mantener una vista del directorio real en el disco, ni existe ninguna forma de que python insista en que el sistema operativo le informe de cualquier modificación en el directorio.

Todo lo que Python puede hacer es solicitar listados periódicos y diferenciar los resultados para ver si ha habido algún cambio.

Lo mejor que puedes hacer es crear un archivo de semáforo en el directorio que permita a otros procesos saber que tu proceso de Python desea que ningún otro proceso modifique el directorio. Por supuesto, solo observarán el semáforo si los ha progtwigdo explícitamente.