¿Cómo puedo leer archivos de texto grandes en Python, línea por línea, sin cargarlos en la memoria?

239

Necesito leer un archivo grande, línea por línea. Digamos que el archivo tiene más de 5GB y necesito leer cada línea, pero obviamente no quiero usarlo readlines()porque creará una lista muy grande en la memoria.

¿Cómo funcionará el siguiente código para este caso? ¿Está xreadlinesleyendo uno por uno en la memoria? ¿Se necesita la expresión del generador?

f = (line for line in open("log.txt").xreadlines())  # how much is loaded in memory?

f.next()  

Además, ¿qué puedo hacer para leer esto en orden inverso, al igual que el tailcomando Linux ?

Encontré:

http://code.google.com/p/pytailer/

y

" cabeza de pitón, cola y lectura hacia atrás por líneas de un archivo de texto "

¡Ambos funcionaron muy bien!

Bruno Rocha - rochacbruno
fuente
¿Y qué puedo hacer para leer esto de la cola? línea por línea, comenzando en la última línea.
Bruno Rocha - rochacbruno
esta debería ser una pregunta separada
cmcginty

Respuestas:

311

Proporcioné esta respuesta porque Keith, aunque breve, no cierra el archivo explícitamente

with open("log.txt") as infile:
    for line in infile:
        do_something_with(line)
John La Rooy
fuente
32
la pregunta sigue siendo, "for line in infile" cargará mis 5 GB de líneas en la memoria? y, ¿Cómo puedo leer desde la cola?
Bruno Rocha - rochacbruno
68
@rochacbruno, solo lee una línea a la vez. Cuando se lea la siguiente línea, la anterior se recolectará a menos que haya almacenado una referencia a ella en otro lugar
John La Rooy
1
@rochacbruno, desafortunadamente, leer las líneas en orden inverso no es tan fácil de hacer de manera eficiente. En general, querrá leer desde el final del archivo en trozos de tamaño razonable (por ejemplo, kilobytes a megabytes) y dividirlos en caracteres de nueva línea (o cualquiera que sea el carácter final de línea en su plataforma)
John La Rooy
44
¡Gracias! Encontré la solución de cola stackoverflow.com/questions/5896079/…
Bruno Rocha - rochacbruno
1
@bawejakunal, ¿Quieres decir si una línea es demasiado larga para cargarla en la memoria de una vez? Eso es inusual para un archivo de texto . En lugar de usar el forbucle que itera sobre las líneas, puede usarlo chunk = infile.read(chunksize)para leer fragmentos de tamaño limitado independientemente de su contenido. Tendrá que buscar nuevas líneas dentro de los fragmentos.
John La Rooy
60

Todo lo que necesita hacer es usar el objeto de archivo como iterador.

for line in open("log.txt"):
    do_something_with(line)

Aún mejor es usar el administrador de contexto en las versiones recientes de Python.

with open("log.txt") as fileobject:
    for line in fileobject:
        do_something_with(line)

Esto también cerrará automáticamente el archivo.

Keith
fuente
2
¿Eso no está cargando todo el archivo en la memoria?
Bruno Rocha - rochacbruno
17

Un enfoque de la vieja escuela:

fh = open(file_name, 'rt')
line = fh.readline()
while line:
    # do stuff with line
    line = fh.readline()
fh.close()
PTBNL
fuente
2
Comentario menor: por seguridad de excepción, se recomienda utilizar la declaración 'con', en su caso "con abierto (nombre de archivo, 'rt') como fh:"
prokher
16
@prokher: Sí, pero llamé a esto "vieja escuela".
PTBNL
15

Es mejor usar un iterador en su lugar. Relevante: http://docs.python.org/library/fileinput.html

De los documentos:

import fileinput
for line in fileinput.input("filename"):
    process(line)

Esto evitará copiar todo el archivo en la memoria a la vez.

Mikola
fuente
Aunque los documentos muestran el fragmento como "uso típico", su uso no llama al close()método del FileInputobjeto de clase devuelto cuando finaliza el ciclo, por lo que evitaría usarlo de esta manera. En Python 3.2 finalmente se han hecho fileinputcompatibles con el protocolo del administrador de contexto que aborda este problema (pero el código aún no se escribiría de la manera que se muestra).
Martineau
7

Esto es lo que debe hacer si no tiene nuevas líneas en el archivo:

with open('large_text.txt') as f:
  while True:
    c = f.read(1024)
    if not c:
      break
    print(c)
Ariel Cabib
fuente
Si bien me gusta este método, corre el riesgo de que la línea de su texto se divida en trozos. Vi esto personalmente, lo que significa que si está buscando sstring en el archivo como lo estaba yo, extrañaría algunos porque la línea en la que estaban se rompió en pedazos. ¿Hay alguna forma de evitar esto? El uso de líneas de lectura no funcionó bien, ya que obtuve errores de cuenta @Ariel Cabib
edo101
6

Por favor intente esto:

with open('filename','r',buffering=100000) as f:
    for line in f:
        print line
jyoti das
fuente
¿por favor explique?
Nikhil VJ
3
De los documentos oficiales de Python: enlace El argumento de almacenamiento en búfer opcional especifica el tamaño de búfer deseado del archivo: 0 significa sin búfer, 1 significa búfer de línea, cualquier otro valor positivo significa usar un búfer de (aproximadamente) ese tamaño (en bytes). Un almacenamiento en búfer negativo significa usar el valor predeterminado del sistema, que generalmente está protegido en línea para dispositivos tty y totalmente protegido para otros archivos. Si se omite, se usa el valor predeterminado del sistema
jyoti das
Me salvó el día, en mi caso, con archivos de ~ ~ 4 gb con dos controladores de archivos (uno leyó, el otro escribió) ¡Python estaba colgando y ahora está bien! Gracias.
Xelt
@jyotidas Si bien me gusta este método, corres el riesgo de que la línea de tu texto se divida en trozos. Vi esto personalmente, lo que significa que si está buscando sstring en el archivo como lo estaba yo, extrañaría algunos porque la línea en la que estaban se rompió en pedazos. ¿Hay alguna forma de evitar esto? El uso de líneas de
lectura
3

No podía creer que pudiera ser tan fácil como lo hizo parecer la respuesta de @ john-la-rooy. Entonces, recreé el cpcomando usando lectura y escritura línea por línea. Es LOCO RÁPIDO.

#!/usr/bin/env python3.6

import sys

with open(sys.argv[2], 'w') as outfile:
    with open(sys.argv[1]) as infile:
        for line in infile:
            outfile.write(line)
Bruno Bronosky
fuente
NOTA: Debido a que Python readlineestandariza las terminaciones de línea, esto tiene el efecto secundario de convertir documentos con terminaciones de línea DOS a terminaciones de \r\nlínea Unix de \n. Toda mi razón para buscar este tema fue que necesitaba convertir un archivo de registro que recibe una mezcla de terminaciones de línea (porque el desarrollador utilizó ciegamente varias bibliotecas .NET). Me sorprendió descubrir que después de mi prueba de velocidad inicial, no necesitaba regresar a rstriplas líneas. ¡Ya era perfecto!
Bruno Bronosky
2

El proyecto Blaze ha recorrido un largo camino en los últimos 6 años. Tiene una API simple que cubre un subconjunto útil de características de pandas.

dask.dataframe se encarga de fragmentar internamente, admite muchas operaciones en paralelo y le permite exportar rebanadas a pandas fácilmente para operaciones en memoria.

import dask.dataframe as dd

df = dd.read_csv('filename.csv')
df.head(10)  # return first 10 rows
df.tail(10)  # return last 10 rows

# iterate rows
for idx, row in df.iterrows():
    ...

# group by my_field and return mean
df.groupby(df.my_field).value.mean().compute()

# slice by column
df[df.my_field=='XYZ'].compute()
jpp
fuente
2

Aquí está el código para cargar archivos de texto de cualquier tamaño sin causar problemas de memoria. Admite archivos de tamaño gigabytes

https://gist.github.com/iyvinjose/e6c1cb2821abd5f01fd1b9065cbc759d

descargue el archivo data_loading_utils.py e impórtelo a su código

uso

import data_loading_utils.py.py
file_name = 'file_name.ext'
CHUNK_SIZE = 1000000


def process_lines(data, eof, file_name):

    # check if end of file reached
    if not eof:
         # process data, data is one single line of the file

    else:
         # end of file reached

data_loading_utils.read_lines_from_file_as_data_chunks(file_name, chunk_size=CHUNK_SIZE, callback=self.process_lines)

El método process_lines es la función de devolución de llamada. Se llamará a todas las líneas, con datos de parámetros que representan una sola línea del archivo a la vez.

Puede configurar la variable CHUNK_SIZE dependiendo de las configuraciones de hardware de su máquina.

Iyvin Jose
fuente
Si bien me gusta este método, corre el riesgo de que la línea de su texto se divida en trozos. Vi esto personalmente, lo que significa que si está buscando sstring en el archivo como lo estaba yo, extrañaría algunos porque la línea en la que estaban se rompió en pedazos. ¿Hay alguna forma de evitar esto? El uso de líneas de
lectura
0

¿Qué tal esto? Divida su archivo en fragmentos y luego léalo línea por línea, porque cuando lea un archivo, su sistema operativo almacenará en caché la siguiente línea. Si está leyendo el archivo línea por línea, no está haciendo un uso eficiente de la información almacenada en caché.

En su lugar, divida el archivo en fragmentos y cargue todo el fragmento en la memoria y luego realice el procesamiento.

def chunks(file,size=1024):
    while 1:

        startat=fh.tell()
        print startat #file's object current position from the start
        fh.seek(size,1) #offset from current postion -->1
        data=fh.readline()
        yield startat,fh.tell()-startat #doesnt store whole list in memory
        if not data:
            break
if os.path.isfile(fname):
    try:
        fh=open(fname,'rb') 
    except IOError as e: #file --> permission denied
        print "I/O error({0}): {1}".format(e.errno, e.strerror)
    except Exception as e1: #handle other exceptions such as attribute errors
        print "Unexpected error: {0}".format(e1)
    for ele in chunks(fh):
        fh.seek(ele[0])#startat
        data=fh.read(ele[1])#endat
        print data
Arohi Gupta
fuente
Esto parece prometedor. ¿Se está cargando por bytes o por líneas? Me temo que las líneas se rompen si es por bytes ... ¿cómo podemos cargar digamos 1000 líneas a la vez y procesar eso?
Nikhil VJ
0

¡Gracias! Recientemente me he convertido a Python 3 y me he sentido frustrado al usar readlines (0) para leer archivos grandes. Esto resolvió el problema. Pero para obtener cada línea, tuve que hacer un par de pasos adicionales. Cada línea fue precedida por una "b", que supongo que estaba en formato binario. El uso de "decode (utf-8)" lo cambió ascii.

Luego tuve que eliminar un "= \ n" en el medio de cada línea.

Luego dividí las líneas en la nueva línea.

b_data=(fh.read(ele[1]))#endat This is one chunk of ascii data in binary format
        a_data=((binascii.b2a_qp(b_data)).decode('utf-8')) #Data chunk in 'split' ascii format
        data_chunk = (a_data.replace('=\n','').strip()) #Splitting characters removed
        data_list = data_chunk.split('\n')  #List containing lines in chunk
        #print(data_list,'\n')
        #time.sleep(1)
        for j in range(len(data_list)): #iterate through data_list to get each item 
            i += 1
            line_of_data = data_list[j]
            print(line_of_data)

Aquí está el código que comienza justo encima de "imprimir datos" en el código de Arohi.

John Haynes
fuente
0

Demostré un enfoque de acceso aleatorio de nivel de byte paralelo aquí en esta otra pregunta:

Obtener el número de líneas en un archivo de texto sin líneas de lectura

Algunas de las respuestas ya proporcionadas son agradables y concisas. Me gustan algunos de ellos. Pero realmente depende de lo que quieras hacer con los datos que están en el archivo. En mi caso, solo quería contar líneas, lo más rápido posible en archivos de texto grandes. Mi código se puede modificar para hacer otras cosas, por supuesto, como cualquier código.

Geoffrey Anderson
fuente
0

La mejor solución que encontré con respecto a esto, y lo probé en un archivo de 330 MB.

lineno = 500
line_length = 8
with open('catfour.txt', 'r') as file:
    file.seek(lineno * (line_length + 2))
    print(file.readline(), end='')

Donde line_length es el número de caracteres en una sola línea. Por ejemplo, "abcd" tiene una longitud de línea 4.

He agregado 2 en longitud de línea para omitir el carácter '\ n' y pasar al siguiente carácter.

Ali Sajjad
fuente
-1

Esto puede ser útil cuando desee trabajar en paralelo y leer solo fragmentos de datos, pero manténgalo limpio con nuevas líneas.

def readInChunks(fileObj, chunkSize=1024):
    while True:
        data = fileObj.read(chunkSize)
        if not data:
            break
        while data[-1:] != '\n':
            data+=fileObj.read(1)
        yield data
Adán
fuente
-10
f=open('filename','r').read()
f1=f.split('\n')
for i in range (len(f1)):
    do_something_with(f1[i])

espero que esto ayude.

Sainik Kr Mahata
fuente
55
¿No leería esto todo el archivo en la memoria? La pregunta pregunta explícitamente cómo evitar eso, por lo tanto, esto no responde la pregunta.
Fermi paradoja