¿Método perezoso para leer archivos grandes en Python?

Tengo un archivo muy grande de 4GB y cuando bash leerlo, mi computadora se cuelga. Así que quiero leerlo pieza por pieza y luego de procesar cada pieza, guarde la pieza procesada en otro archivo y lea la siguiente pieza.

¿Hay algún método para yield estas piezas?

Me encantaría tener un método perezoso .

Para escribir una función perezosa, solo usa yield :

 def read_in_chunks(file_object, chunk_size=1024): """Lazy function (generator) to read a file piece by piece. Default chunk size: 1k.""" while True: data = file_object.read(chunk_size) if not data: break yield data f = open('really_big_file.dat') for piece in read_in_chunks(f): process_data(piece) 

Otra opción sería usar iter y una función auxiliar:

 f = open('really_big_file.dat') def read1k(): return f.read(1024) for piece in iter(read1k, ''): process_data(piece) 

Si el archivo está basado en líneas, el objeto de archivo ya es un generador de líneas perezoso:

 for line in open('really_big_file.dat'): process_data(line) 

Si su computadora, sistema operativo y python son de 64 bits , entonces puede usar el módulo mmap para asignar el contenido del archivo a la memoria y acceder a él con índices y segmentos. Aquí un ejemplo de la documentación:

 import mmap with open("hello.txt", "r+") as f: # memory-map the file, size 0 means whole file map = mmap.mmap(f.fileno(), 0) # read content via standard file methods print map.readline() # prints "Hello Python!" # read content via slice notation print map[:5] # prints "Hello" # update content using slice notation; # note that new content must have same size map[6:] = " world!\n" # ... and read again using standard file methods map.seek(0) print map.readline() # prints "Hello world!" # close the map map.close() 

Si su computadora, sistema operativo o python son de 32 bits , entonces los archivos de gran tamaño pueden reservar gran parte de su espacio de direcciones y dejar de lado su progtwig de memoria.

file.readlines () toma un argumento de tamaño opcional que se aproxima al número de líneas leídas en las líneas devueltas.

 bigfile = open('bigfilename','r') tmp_lines = bigfile.readlines(BUF_SIZE) while tmp_lines: process([line for line in tmp_lines]) tmp_lines = bigfile.readlines(BUF_SIZE) 

Eche un vistazo a esta publicación en Neopythonic : “Clasificación de un millón de enteros de 32 bits en 2 MB de RAM usando Python”

Ya hay muchas respuestas buenas, pero recientemente tuve un problema similar y la solución que necesitaba no se encuentra aquí, así que pensé que podría complementar este hilo.

El 80% del tiempo, necesito leer los archivos línea por línea. Luego, como se sugiere en esta respuesta , desea utilizar el objeto de archivo como un generador perezoso:

 with open('big.csv') as f: for line in f: process(line) 

Sin embargo, recientemente me encontré con un csv muy grande (casi) de una sola línea, donde el separador de fila no era, en realidad, '\n' sino '|' .

  • La lectura línea por línea no era una opción, pero aún necesitaba procesarla fila por fila.
  • Convertir '|' hasta '\n' antes del procesamiento también estaba fuera de cuestión, porque algunos de los campos de este csv contenían '\n' (entrada de usuario de texto libre).
  • El uso de la biblioteca csv también se descartó porque el hecho de que, al menos en las primeras versiones de la biblioteca, es difícil leer la línea de entrada por línea .

Se me ocurrió el siguiente fragmento de código:

 def rows(f, chunksize=1024, sep='|'): """ Read a file where the row separator is '|' lazily. Usage: >>> with open('big.csv') as f: >>> for r in rows(f): >>> process(row) """ incomplete_row = None while True: chunk = f.read(chunksize) if not chunk: # End of file if incomplete_row is not None: yield incomplete_row break # Split the chunk as long as possible while True: i = chunk.find(sep) if i == -1: break # If there is an incomplete row waiting to be yielded, # prepend it and set it back to None if incomplete_row is not None: yield incomplete_row + chunk[:i] incomplete_row = None else: yield chunk[:i] chunk = chunk[i+1:] # If the chunk contained no separator, it needs to be appended to # the current incomplete row. if incomplete_row is not None: incomplete_row += chunk else: incomplete_row = chunk 

Lo he probado con éxito en archivos grandes y con diferentes tamaños de trozos (incluso probé un tamaño de trozo de 1 byte, solo para asegurarme de que el algoritmo no depende del tamaño).

 f = ... # file-like object, ie supporting read(size) function and # returning empty string '' when there is nothing to read def chunked(file, chunk_size): return iter(lambda: file.read(chunk_size), '') for data in chunked(f, 65536): # process the data 

ACTUALIZACIÓN: El enfoque se explica mejor en https://stackoverflow.com/a/4566523/38592

Creo que podemos escribir así:

 def read_file(path, block_size=1024): with open(path, 'rb') as f: while True: piece = f.read(block_size) if piece: yield piece else: return for piece in read_file(path): process_piece(piece) 

No se me permite comentar debido a mi baja reputación, pero la solución SilentGhosts debería ser mucho más fácil con file.readlines ([sizehint])

métodos de archivo de python

edición: SilentGhost tiene razón, pero esto debería ser mejor que:

 s = "" for i in xrange(100): s += file.next() 

Estoy en una situación algo similar. No está claro si conoces el tamaño del trozo en bytes; Normalmente no, pero se conoce el número de registros (líneas) que se requiere:

 def get_line(): with open('4gb_file') as file: for i in file: yield i lines_required = 100 gen = get_line() chunk = [i for i, j in zip(gen, range(lines_required))] 

Actualización : gracias nosklo. Esto es lo que quise decir. Casi funciona, excepto que pierde una línea ‘entre’ trozos.

 chunk = [next(gen) for i in range(lines_required)] 

Hace el truco sin perder ninguna línea, pero no se ve muy bien.

Para procesar línea por línea, esta es una solución elegante:

  def stream_lines(file_name): file = open(file_name) while True: line = file.readline() if not line: file.close() break yield line 

Mientras no haya líneas en blanco.

Puedes usar el siguiente código.

 file_obj = open('big_file') 

open () devuelve un objeto de archivo

luego usa os.stat para obtener el tamaño

 file_size = os.stat('big_file').st_size for i in range( file_size/1024): print file_obj.read(1024)