¿Cómo leer un archivo grande, línea por línea?

536

Quiero iterar sobre cada línea de un archivo completo. Una forma de hacerlo es leer todo el archivo, guardarlo en una lista y luego pasar la línea de interés. Este método usa mucha memoria, por lo que estoy buscando una alternativa.

Mi código hasta ahora:

for each_line in fileinput.input(input_file):
    do_something(each_line)

    for each_line_again in fileinput.input(input_file):
        do_something(each_line_again)

La ejecución de este código da un mensaje de error: device active.

¿Alguna sugerencia?

El propósito es calcular la similitud de cadena en pares, es decir, para cada línea en el archivo, quiero calcular la distancia de Levenshtein con cada otra línea.

384X21
fuente
44
¿Por qué necesita leer todo el archivo nuevamente para cada línea? Tal vez si dijera lo que está tratando de lograr, alguien podría sugerir un mejor enfoque.
JJJ

Respuestas:

1269

La forma correcta y totalmente pitónica de leer un archivo es la siguiente:

with open(...) as f:
    for line in f:
        # Do something with 'line'

La withinstrucción se encarga de abrir y cerrar el archivo, incluso si se genera una excepción en el bloque interno. Los for line in ftrata el objeto archivo fcomo un iterable, que utiliza automáticamente búfer de E / S y la gestión de memoria por lo que no tiene que preocuparse de archivos de gran tamaño.

Debe haber una, y preferiblemente solo una, forma obvia de hacerlo.

Katriel
fuente
14
Sí, esta es la mejor versión con Python 2.6 y superior
Simon Bergot
3
Personalmente prefiero generadores y corutinas para tratar con tuberías de datos.
jldupont el
44
¿Cuál sería la mejor estrategia si un archivo es un archivo de texto enorme pero con una línea y la idea es procesar palabras?
mfcabrera
44
¿Alguien podría explicar cómo for line in f:está funcionando? Quiero decir, ¿cómo es posible iterar sobre un objeto de archivo?
piratea
11
Si itera sobre un objeto, Python busca en la lista de métodos de objeto uno especial llamado __iter__, que le dice qué hacer. Los objetos de archivo definen este método especial para devolver un iterador sobre las líneas. (Aproximadamente.)
Katriel
130

Dos formas eficientes de memoria en orden de clasificación (la primera es la mejor):

  1. uso de with- compatible con python 2.5 y superior
  2. uso de yieldsi realmente desea tener control sobre cuánto leer

1. uso de with

withes la forma pitónica agradable y eficiente de leer archivos grandes. ventajas - 1) el objeto de archivo se cierra automáticamente después de salir del withbloque de ejecución. 2) manejo de excepciones dentro del withbloque. 3) el forbucle de memoria recorre el fobjeto de archivo línea por línea. internamente almacena E / S en búfer (optimizado para operaciones costosas de E / S) y gestión de memoria.

with open("x.txt") as f:
    for line in f:
        do something with data

2. uso de yield

A veces, uno podría desear un control más detallado sobre cuánto leer en cada iteración. En ese caso, use iter y rendimiento . Tenga en cuenta que con este método se necesita cerrar explícitamente el archivo al final.

def readInChunks(fileObj, chunkSize=2048):
    """
    Lazy function to read a file piece by piece.
    Default chunk size: 2kB.
    """
    while True:
        data = fileObj.read(chunkSize)
        if not data:
            break
        yield data

f = open('bigFile')
for chuck in readInChunks(f):
    do_something(chunk)
f.close()

Las trampas y en aras de la exhaustividad : los métodos a continuación no son tan buenos ni tan elegantes para leer archivos grandes, pero lea para obtener una comprensión completa.

En Python, la forma más común de leer líneas de un archivo es hacer lo siguiente:

for line in open('myfile','r').readlines():
    do_something(line)

Sin embargo, cuando se hace esto, la readlines()función (lo mismo se aplica a la read()función) carga todo el archivo en la memoria y luego lo repite. Un enfoque un poco mejor (los dos primeros métodos mencionados son los mejores) para archivos grandes es usar el fileinputmódulo, de la siguiente manera:

import fileinput

for line in fileinput.input(['myfile']):
    do_something(line)

la fileinput.input()llamada lee líneas secuencialmente, pero no las guarda en la memoria después de haberlas leído o simplemente así, ya que fileen python es iterable.

Referencias

  1. Python con declaración
Srikar Appalaraju
fuente
99
-1 Básicamente, nunca es una buena idea hacerlo for line in open(...).readlines(): <do stuff>. ¡¿Por que lo harias?! Acaba de perder todo el beneficio del iterador IO con memoria intermedia inteligente de Python sin ningún beneficio.
Katriel el
55
@Srikar: hay un momento y un lugar para dar todas las soluciones posibles a un problema; enseñarle a un principiante cómo hacer la entrada de archivos no es así Tener la respuesta correcta enterrada en la parte inferior de una larga publicación llena de respuestas incorrectas no es una buena enseñanza.
Katriel el
66
@Srikar: Puede mejorar su publicación significativamente colocando la forma correcta en la parte superior, luego mencionando readlinesy explicando por qué no es algo bueno (porque lee el archivo en la memoria), luego explica qué hace el fileinputmódulo y por qué es posible que desee usarlo sobre los otros métodos, luego explicar cómo fragmentar el archivo mejora el IO y dar un ejemplo de la función de fragmentación (pero menciona que Python ya lo hace por usted, por lo que no es necesario). Pero dar cinco maneras de resolver un problema simple, cuatro de las cuales están mal en este caso, no es bueno.
Katriel el
2
Lo que agregue por completo, agréguelo al final, no primero. Primero muestra la forma correcta.
m000
66
@katrielalex revisó mi respuesta y descubrió que justifica la reestructuración. Puedo ver cómo la respuesta anterior podría causar confusión. Esperemos que esto quede claro para futuros usuarios.
Srikar Appalaraju
37

Para quitar nuevas líneas:

with open(file_path, 'rU') as f:
    for line_terminated in f:
        line = line_terminated.rstrip('\n')
        ...

Con el apoyo de nueva línea universal de todas las líneas de archivo de texto parecen ser terminada con '\n', cualesquiera que sean los terminadores en el archivo, '\r', '\n', o '\r\n'.

EDITAR: para especificar el soporte universal de nueva línea:

  • Python 2 en Unix - open(file_path, mode='rU')- requerido [gracias @Dave ]
  • Python 2 en Windows - open(file_path, mode='rU')- opcional
  • Python 3 - open(file_path, newline=None)- opcional

El newlineparámetro solo es compatible con Python 3 y su valor predeterminado es None. El modeparámetro predeterminado es 'r'en todos los casos. El Uestá en desuso en Python 3. En Python 2 en Windows aparece algún otro mecanismo para traducir \r\na \n.

Documentos:

Para preservar los terminadores de línea nativos:

with open(file_path, 'rb') as f:
    with line_native_terminated in f:
        ...

El modo binario aún puede analizar el archivo en líneas con in. Cada línea tendrá los terminadores que tenga en el archivo.

Gracias a la respuesta de @katrielalex , el documento open () de Python y los experimentos de iPython .

Bob Stein
fuente
1
En Python 2.7 tuve open(file_path, 'rU')que habilitar nuevas líneas universales.
Dave
17

Esta es una posible forma de leer un archivo en Python:

f = open(input_file)
for line in f:
    do_stuff(line)
f.close()

no asigna una lista completa. Se itera sobre las líneas.

Simon Bergot
fuente
2
Si bien esto funciona, definitivamente no es la forma canónica. La forma canónica es usar un contenedor de contexto, como with open(input_file) as f:. Esto le ahorra f.close()y se asegura de que no olvide accidentalmente cerrarlo. Evita pérdidas de memoria y todo, muy importante al leer archivos.
Mástil
1
Como dijo @Mast, esa no es la forma canónica, así que desestima tu voto.
azuax
12

Algún contexto por adelantado de dónde vengo. Los fragmentos de código están al final.

Cuando puedo, prefiero usar una herramienta de código abierto como H2O para hacer lecturas de archivos CSV paralelos de súper alto rendimiento, pero esta herramienta tiene un conjunto de características limitado. Terminé escribiendo mucho código para crear canalizaciones de ciencia de datos antes de alimentar al clúster H2O para el aprendizaje supervisado propiamente dicho.

He estado leyendo archivos como el conjunto de datos HIGGS de 8 GB del repositorio UCI e incluso archivos CSV de 40 GB para fines de ciencia de datos significativamente más rápido al agregar mucho paralelismo con el objeto de grupo de la biblioteca de multiprocesamiento y la función de mapa. Por ejemplo, la agrupación con búsquedas vecinas más cercanas y también los algoritmos de agrupación DBSCAN y Markov requieren cierta delicadeza de programación paralela para evitar algunos problemas de memoria y tiempo de reloj de pared muy desafiantes.

Por lo general, me gusta dividir el archivo en hileras en partes usando herramientas gnu primero y luego glob-filemask a todos para encontrarlos y leerlos en paralelo en el programa python. Yo uso algo así como más de 1000 archivos parciales comúnmente. Hacer estos trucos ayuda enormemente con la velocidad de procesamiento y los límites de memoria.

El pandas dataframe.read_csv tiene un solo subproceso, por lo que puede hacer estos trucos para hacer que los pandas sean bastante más rápidos ejecutando un mapa () para ejecución paralela. Puede usar htop para ver que con pandas secuenciales antiguos simples dataframe.read_csv, 100% de CPU en un solo núcleo es el cuello de botella real en pd.read_csv, no el disco en absoluto.

Debo agregar que estoy usando un SSD en el bus de tarjeta de video rápido, no un HD giratorio en el bus SATA6, más 16 núcleos de CPU.

Además, otra técnica que descubrí que funciona muy bien en algunas aplicaciones es que el archivo CSV paralelo lee todo dentro de un archivo gigante, comenzando cada trabajador con un desplazamiento diferente en el archivo, en lugar de dividir previamente un archivo grande en muchos archivos de partes. Use el archivo de búsqueda de python () y tell () en cada trabajador paralelo para leer el archivo de texto grande en tiras, en diferentes ubicaciones de inicio de byte y final de byte de desplazamiento en el archivo grande, todo al mismo tiempo al mismo tiempo. Puede hacer un finge regex en los bytes y devolver el recuento de saltos de línea. Esta es una suma parcial. Finalmente, sume las sumas parciales para obtener la suma global cuando la función de mapa regrese después de que los trabajadores hayan terminado.

A continuación se muestran algunos puntos de referencia de ejemplo que utilizan el truco de desplazamiento de byte paralelo:

Yo uso 2 archivos: HIGGS.csv es de 8 GB. Es del repositorio de aprendizaje automático UCI. all_bin .csv tiene 40.4 GB y es de mi proyecto actual. Utilizo 2 programas: el programa GNU wc que viene con Linux y el programa puro python fastread.py que desarrollé.

HP-Z820:/mnt/fastssd/fast_file_reader$ ls -l /mnt/fastssd/nzv/HIGGS.csv
-rw-rw-r-- 1 8035497980 Jan 24 16:00 /mnt/fastssd/nzv/HIGGS.csv

HP-Z820:/mnt/fastssd$ ls -l all_bin.csv
-rw-rw-r-- 1 40412077758 Feb  2 09:00 all_bin.csv

ga@ga-HP-Z820:/mnt/fastssd$ time python fastread.py --fileName="all_bin.csv" --numProcesses=32 --balanceFactor=2
2367496

real    0m8.920s
user    1m30.056s
sys 2m38.744s

In [1]: 40412077758. / 8.92
Out[1]: 4530501990.807175

Eso es alrededor de 4.5 GB / s, o 45 Gb / s, velocidad de extracción de archivos. Eso no es un disco duro giratorio, amigo mío. Eso es realmente un SSD Samsung Pro 950.

A continuación se muestra el punto de referencia de velocidad para el mismo archivo que cuenta gnu wc, un programa compilado en C puro.

Lo que es genial es que puedes ver que mi programa de Python puro esencialmente coincidía con la velocidad del programa C compilado por gnu wc en este caso. Python se interpreta pero C se compila, por lo que esta es una hazaña de velocidad bastante interesante, creo que estaría de acuerdo. Por supuesto, wc realmente necesita ser cambiado a un programa paralelo, y luego realmente superaría a mi programa Python. Pero tal como está hoy, gnu wc es solo un programa secuencial. Haces lo que puedes y Python puede hacerlo en paralelo hoy. La compilación de Cython podría ayudarme (en algún otro momento). Además, los archivos asignados a la memoria aún no se exploraron.

HP-Z820:/mnt/fastssd$ time wc -l all_bin.csv
2367496 all_bin.csv

real    0m8.807s
user    0m1.168s
sys 0m7.636s


HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=16 --balanceFactor=2
11000000

real    0m2.257s
user    0m12.088s
sys 0m20.512s

HP-Z820:/mnt/fastssd/fast_file_reader$ time wc -l HIGGS.csv
11000000 HIGGS.csv

real    0m1.820s
user    0m0.364s
sys 0m1.456s

Conclusión: la velocidad es buena para un programa de Python puro en comparación con un programa en C. Sin embargo, no es lo suficientemente bueno para usar el programa Python puro sobre el programa C, al menos con el propósito de contar líneas. En general, la técnica se puede utilizar para otro procesamiento de archivos, por lo que este código de Python sigue siendo bueno.

Pregunta: ¿Compilar la expresión regular solo una vez y pasarla a todos los trabajadores mejorará la velocidad? Respuesta: El precompilación de Regex NO ayuda en esta aplicación. Supongo que la razón es que la sobrecarga de la serialización y creación de procesos para todos los trabajadores es dominante.

Una cosa más. ¿La lectura de archivos CSV paralelos incluso ayuda? ¿Es el disco el cuello de botella o es la CPU? Muchas de las llamadas respuestas mejor calificadas en stackoverflow contienen la sabiduría de desarrollo común de que solo necesita un hilo para leer un archivo, lo mejor que puede hacer, dicen. ¿Están seguros, sin embargo?

Vamos a averiguar:

HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=16 --balanceFactor=2
11000000

real    0m2.256s
user    0m10.696s
sys 0m19.952s

HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=1 --balanceFactor=1
11000000

real    0m17.380s
user    0m11.124s
sys 0m6.272s

Oh si, si lo hace. La lectura de archivos paralelos funciona bastante bien. ¡Bueno, allá vas!

PD. En caso de que algunos de ustedes quisieran saber, ¿qué pasaría si el balanceFactor fuera 2 cuando utilizara un solo proceso de trabajo? Bueno, es horrible.

HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=1 --balanceFactor=2
11000000

real    1m37.077s
user    0m12.432s
sys 1m24.700s

Partes clave del programa python fastread.py:

fileBytes = stat(fileName).st_size  # Read quickly from OS how many bytes are in a text file
startByte, endByte = PartitionDataToWorkers(workers=numProcesses, items=fileBytes, balanceFactor=balanceFactor)
p = Pool(numProcesses)
partialSum = p.starmap(ReadFileSegment, zip(startByte, endByte, repeat(fileName))) # startByte is already a list. fileName is made into a same-length list of duplicates values.
globalSum = sum(partialSum)
print(globalSum)


def ReadFileSegment(startByte, endByte, fileName, searchChar='\n'):  # counts number of searchChar appearing in the byte range
    with open(fileName, 'r') as f:
        f.seek(startByte-1)  # seek is initially at byte 0 and then moves forward the specified amount, so seek(5) points at the 6th byte.
        bytes = f.read(endByte - startByte + 1)
        cnt = len(re.findall(searchChar, bytes)) # findall with implicit compiling runs just as fast here as re.compile once + re.finditer many times.
    return cnt

La definición para PartitionDataToWorkers es simplemente un código secuencial ordinario. Lo dejé fuera en caso de que alguien más quiera practicar algo sobre cómo es la programación paralela. Regale gratuitamente las partes más difíciles: el código paralelo probado y en funcionamiento, para su beneficio de aprendizaje.

Gracias a: El proyecto de código abierto H2O, de Arno y Cliff y el personal de H2O por su excelente software y videos instructivos, que me han inspirado para este lector de desplazamiento de byte paralelo de alto rendimiento de Python puro como se muestra arriba. H2O realiza la lectura paralela de archivos usando java, es ejecutable por los programas python y R, y es una locura rápida, más rápida que cualquier otra cosa en el planeta al leer grandes archivos CSV.

Geoffrey Anderson
fuente
Esto es, básicamente, trozos paralelos. Además, espero que SSD y Flash sean los únicos dispositivos de almacenamiento compatibles con esta técnica. Spinning HD es poco probable que sea compatible.
Geoffrey Anderson
1
¿Cómo se explicaron los archivos de disco de almacenamiento en caché del sistema operativo?
JamesThomasMoon1979
5

Katrielalex proporcionó la manera de abrir y leer un archivo.

Sin embargo, según su algoritmo, lee el archivo completo para cada línea del archivo. Eso significa que la cantidad total de lectura de un archivo, y el cálculo de la distancia de Levenshtein , se realizará N * N si N es la cantidad de líneas en el archivo. Como le preocupa el tamaño del archivo y no desea mantenerlo en la memoria, me preocupa el tiempo de ejecución cuadrático resultante . Su algoritmo está en la clase O (n ^ 2) de algoritmos que a menudo se pueden mejorar con especialización.

Sospecho que ya conoce la compensación de memoria versus tiempo de ejecución aquí, pero tal vez desee investigar si hay una manera eficiente de calcular múltiples distancias de Levenshtein en paralelo. Si es así, sería interesante compartir su solución aquí.

¿Cuántas líneas tienen sus archivos y en qué tipo de máquina (potencia de memoria y CPU) tiene que funcionar su algoritmo y cuál es el tiempo de ejecución tolerado?

El código se vería así:

with f_outer as open(input_file, 'r'):
    for line_outer in f_outer:
        with f_inner as open(input_file, 'r'):
            for line_inner in f_inner:
                compute_distance(line_outer, line_inner)

Pero las preguntas son: ¿cómo almacena las distancias (matriz?) Y puede obtener una ventaja de preparar, por ejemplo, la línea externa para el procesamiento, o el almacenamiento en caché de algunos resultados intermedios para su reutilización.

cfi
fuente
Mi punto es que esta publicación no contiene una respuesta a la pregunta, ¡solo algunas preguntas más! En mi opinión, sería más adecuado como comentario.
Katriel el
1
@katriealex: Err. Extraño. ¿Viste los bucles anidados, expandiendo tu propia respuesta para que se ajuste a la pregunta real? Puedo eliminar mis preguntas aquí de mi respuesta, y todavía hay suficiente contenido para garantizar que se proporcione esto como una respuesta, aunque parcial. También podría aceptar si editara su propia respuesta para incluir el ejemplo de bucle anidado, que fue preguntado explícitamente por la pregunta, y luego puedo eliminar mi propia respuesta felizmente. Pero un voto negativo es algo que no entiendo en absoluto.
cfi
Lo suficientemente justo; Realmente no veo demostrar los bucles anidados como respuesta a la pregunta, pero supongo que está muy orientado a los principiantes. Voto negativo eliminado.
Katriel
3
#Using a text file for the example
with open("yourFile.txt","r") as f:
    text = f.readlines()
for line in text:
    print line
  • Abra su archivo para leer (r)
  • Lea todo el archivo y guarde cada línea en una lista (texto)
  • Recorra la lista imprimiendo cada línea.

Si desea, por ejemplo, verificar una línea específica para una longitud mayor a 10, trabaje con lo que ya tiene disponible.

for line in text:
    if len(line) > 10:
        print line
loxsat
fuente
1
No es lo mejor para esta pregunta, pero este código es principalmente útil en caso de que lo que está buscando sea "sorber" (leer todo el archivo de una vez). Ese fue mi caso y Google me trajo aquí. +1. Además, por razones de atomicidad, o si realiza un procesamiento lento en el ciclo, puede terminar más rápido para leer el archivo completo
ntg
1
Además, mejoró un poco el código: 1. no es necesario cerrar después con: ( docs.python.org/2/tutorial/inputoutput.html , busque "Es una buena práctica usar el con la palabra clave ...") 2 . texto puede procesarse después se lee el archivo (ouside del bucle de la ....)
NTG
2

De la documentación de Python para fileinput .input ():

Esto itera sobre las líneas de todos los archivos enumerados sys.argv[1:], por defecto sys.stdinsi la lista está vacía

Además, la definición de la función es:

fileinput.FileInput([files[, inplace[, backup[, mode[, openhook]]]]])

leyendo entre líneas, esto me dice que filespuede ser una lista para que puedas tener algo como:

for each_line in fileinput.input([input_file, input_file]):
  do_something(each_line)

Ver aquí para más información.

KevinDTimm
fuente
2

Recomiendo encarecidamente no utilizar la carga de archivos predeterminada, ya que es terriblemente lenta. Debería examinar las funciones numpy y las funciones IOpro (por ejemplo, numpy.loadtxt ()).

http://docs.scipy.org/doc/numpy/user/basics.io.genfromtxt.html

https://store.continuum.io/cshop/iopro/

Luego puede dividir su operación por pares en trozos:

import numpy as np
import math

lines_total = n    
similarity = np.zeros(n,n)
lines_per_chunk = m
n_chunks = math.ceil(float(n)/m)
for i in xrange(n_chunks):
    for j in xrange(n_chunks):
        chunk_i = (function of your choice to read lines i*lines_per_chunk to (i+1)*lines_per_chunk)
        chunk_j = (function of your choice to read lines j*lines_per_chunk to (j+1)*lines_per_chunk)
        similarity[i*lines_per_chunk:(i+1)*lines_per_chunk,
                   j*lines_per_chunk:(j+1)*lines_per_chunk] = fast_operation(chunk_i, chunk_j) 

¡Casi siempre es mucho más rápido cargar datos en fragmentos y luego realizar operaciones matriciales en él que hacerlo elemento por elemento!

John Haberstroh
fuente
0

¿Necesita leer con frecuencia un archivo grande desde la última lectura de posición?

He creado un script utilizado para cortar un archivo Apache access.log varias veces al día. Entonces necesitaba establecer un cursor de posición en la última línea analizada durante la última ejecución . Para este fin, utilicé file.seek()y file.seek()métodos que permiten el almacenamiento del cursor en el archivo.

Mi código :

ENCODING = "utf8"
CURRENT_FILE_DIR = os.path.dirname(os.path.abspath(__file__))

# This file is used to store the last cursor position
cursor_position = os.path.join(CURRENT_FILE_DIR, "access_cursor_position.log")

# Log file with new lines
log_file_to_cut = os.path.join(CURRENT_FILE_DIR, "access.log")
cut_file = os.path.join(CURRENT_FILE_DIR, "cut_access", "cut.log")

# Set in from_line 
from_position = 0
try:
    with open(cursor_position, "r", encoding=ENCODING) as f:
        from_position = int(f.read())
except Exception as e:
    pass

# We read log_file_to_cut to put new lines in cut_file
with open(log_file_to_cut, "r", encoding=ENCODING) as f:
    with open(cut_file, "w", encoding=ENCODING) as fw:
        # We set cursor to the last position used (during last run of script)
        f.seek(from_position)
        for line in f:
            fw.write("%s" % (line))

    # We save the last position of cursor for next usage
    with open(cursor_position, "w", encoding=ENCODING) as fw:
        fw.write(str(f.tell()))
Samuel Dauzon
fuente
-2

La mejor manera de leer archivos grandes, línea por línea es usar la función de enumeración de Python

with open(file_name, "rU") as read_file:
    for i, row in enumerate(read_file, 1):
        #do something
        #i in line of that line
        #row containts all data of that line
Anurag Misra
fuente
3
¿Por qué es mejor usar enumerate? El único beneficio sobre la respuesta aceptada es que obtienes un índice, que OP no necesita y estás haciendo que el código sea menos legible.
fuyas