Come posso leggere file di testo di grandi dimensioni in Python, riga per riga, senza caricarli in memoria?


239

Devo leggere un file di grandi dimensioni, riga per riga. Diciamo che il file ha più di 5 GB e ho bisogno di leggere ogni riga, ma ovviamente non voglio usarlo readlines()perché creerà un elenco molto grande in memoria.

Come funzionerà il codice seguente per questo caso? Si sta xreadlinesleggendo uno per uno in memoria? È necessaria l'espressione del generatore?

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

f.next()  

Inoltre, cosa posso fare per leggerlo in ordine inverso, proprio come il tailcomando Linux ?

Ho trovato:

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

e

" testa, coda e coda di pitone lette da righe di un file di testo "

Entrambi hanno funzionato molto bene!


E cosa posso fare per leggere questo dalla coda? riga per riga, a partire dall'ultima riga.
Bruno Rocha - rochacbruno,

questa dovrebbe essere una domanda separata
cmcginty,

Risposte:


311

Ho fornito questa risposta perché Keith's, sebbene succinta, non chiude esplicitamente il file

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

32
la domanda è ancora: "per linea in infile" caricherà i miei 5 GB di linee nella memoria? e, come posso leggere dalla coda?
Bruno Rocha - rochacbruno,

68
@rochacbruno, legge solo una riga alla volta. Quando viene letta la riga successiva, la precedente verrà raccolta in modo inutile a meno che tu non abbia memorizzato un riferimento ad essa da qualche altra parte
John La Rooy,

1
@rochacbruno, Sfortunatamente leggere le righe in ordine inverso non è altrettanto facile. Generalmente vorresti leggere dalla fine del file in blocchi di dimensioni ragionevoli (chilobyte in megabyte) e dividere i caratteri di nuova riga (o qualunque sia il carattere di fine riga sulla tua piattaforma)
John La Rooy

4

1
@bawejakunal, vuoi dire se una linea è troppo lunga per essere caricata in memoria contemporaneamente? Questo è insolito per un file di testo . Invece di usare il forciclo che scorre tra le righe, puoi usare chunk = infile.read(chunksize)per leggere blocchi di dimensioni limitate indipendentemente dal loro contenuto. Dovrai cercare all'interno dei blocchi per le nuove righe da solo.
John La Rooy,

60

Tutto quello che devi fare è usare l'oggetto file come iteratore.

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

Ancora meglio sta usando il gestore di contesto nelle recenti versioni di Python.

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

Questo chiuderà automaticamente anche il file.


2
Che non sta caricando l'intero file nella memoria?
Bruno Rocha - rochacbruno,

17

Un approccio di vecchia scuola:

fh = open(file_name, 'rt')
line = fh.readline()
while line:
    # do stuff with line
    line = fh.readline()
fh.close()

2
piccola osservazione: per sicurezza d'eccezione, si consiglia di usare l'istruzione 'with', nel tuo caso "con open (nome file, 'rt') come fh:"
prokher

16
@prokher: Sì, ma ho chiamato questa "vecchia scuola".
PTBNL

15

È preferibile invece utilizzare un iteratore. Pertinente: http://docs.python.org/library/fileinput.html

Dai documenti:

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

Ciò eviterà di copiare l'intero file in memoria contemporaneamente.


Sebbene i documenti mostrino lo snippet come "uso tipico", usarlo non chiama il close()metodo FileInputdell'oggetto di classe restituito al termine del ciclo, quindi eviterei di usarlo in questo modo. In Python 3.2 hanno finalmente reso fileinputcompatibile con il protocollo di gestione del contesto che risolve questo problema (ma il codice non sarebbe ancora stato scritto nel modo mostrato).
martineau,

7

Ecco cosa fai se non hai newline nel file:

with open('large_text.txt') as f:
  while True:
    c = f.read(1024)
    if not c:
      break
    print(c)

Mentre mi piace questo metodo, corri il rischio di avere una linea nel tuo testo spezzata in blocchi. L'ho visto personalmente, il che significa che se stai cercando lo string nel file come me, me ne mancherei perché la linea in cui si trovavano era spezzata in blocchi. C'è un modo per aggirare questo? L'uso di readline non ha funzionato bene perché ho avuto degli errori @Ariel Cabib
edo101

6

Per favore prova questo:

with open('filename','r',buffering=100000) as f:
    for line in f:
        print line

Spiega per favore?
Nikhil VJ

3
Dai docmunet ufficiali di Python: link L'argomento di buffering opzionale specifica la dimensione del buffer desiderata del file: 0 significa non bufferizzato, 1 significa buffer di linea, qualsiasi altro valore positivo significa utilizzare un buffer di (approssimativamente) quella dimensione (in byte). Un buffering negativo significa utilizzare l'impostazione predefinita del sistema, che di solito è buffer di linea per dispositivi tty e completamente buffer per altri file. Se omesso, viene utilizzato il valore predefinito di sistema
jyoti das

Ho salvato la mia giornata, nel mio caso, con> ~ file da 4 GB con due gestori di file (uno letto, l'altro scritto) python era sospeso e ora va bene! Grazie.
Xelt,

@jyotidas Anche se mi piace questo metodo, corri il rischio di avere una linea nel tuo testo spezzata in blocchi. L'ho visto personalmente, il che significa che se stai cercando lo string nel file come me, me ne mancherei perché la linea in cui si trovavano era spezzata in blocchi. C'è un modo per aggirare questo? L'uso di readline non ha funzionato bene perché ho avuto degli errori
edo101,

3

Non riuscivo a credere che potesse essere facile come sembrava la risposta di @ john-la-rooy. Quindi, ho ricreato il cpcomando usando la lettura e la scrittura riga per riga. È PAZZO RAPIDAMENTE.

#!/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)

NOTA: poiché Python readlinestandardizza le terminazioni di riga, ciò ha l'effetto collaterale di convertire documenti con terminazioni di riga DOS in finali di \r\nriga Unix di \n. La mia intera ragione per cercare questo argomento era che dovevo convertire un file di registro che riceve un miscuglio di terminazioni di riga (perché lo sviluppatore ha usato ciecamente varie librerie .NET). Sono rimasto scioccato nello scoprire che dopo il mio test di velocità iniziale, non avevo bisogno di tornare indietro e rstriple linee. Era già perfetto!
Bruno Bronosky,

2

La fiammata progetto ha fatto molta strada negli ultimi 6 anni. Ha una semplice API che copre un utile sottoinsieme di funzionalità di Panda.

dask.dataframe si occupa della suddivisione interna, supporta molte operazioni parallelizzabili e consente di esportare facilmente sezioni in panda per operazioni in 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()

2

Ecco il codice per caricare file di testo di qualsiasi dimensione senza causare problemi di memoria. Supporta file di dimensioni gigabyte

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

scarica il file data_loading_utils.py e importalo nel tuo codice

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)

process_lines metodo è la funzione di callback. Verrà chiamato per tutte le righe, con i dati dei parametri che rappresentano una singola riga del file alla volta.

È possibile configurare la variabile CHUNK_SIZE in base alle configurazioni hardware della macchina.


Mentre mi piace questo metodo, corri il rischio di avere una linea nel tuo testo spezzata in blocchi. L'ho visto personalmente, il che significa che se stai cercando lo string nel file come me, me ne mancherei perché la linea in cui si trovavano era spezzata in blocchi. C'è un modo per aggirare questo? L'uso di readline non ha funzionato bene perché ho avuto degli errori
edo101,

0

Cosa ne pensi di questo? Dividi il tuo file in blocchi e poi leggilo riga per riga, perché quando leggi un file, il tuo sistema operativo memorizzerà nella cache la riga successiva. Se stai leggendo il file riga per riga, non stai facendo un uso efficiente delle informazioni memorizzate nella cache.

Invece, dividi il file in blocchi e carica l'intero blocco in memoria, quindi esegui l'elaborazione.

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

Sembra promettente. Si sta caricando per byte o per linee? Ho paura che le linee vengano spezzate se si tratta di byte .. come possiamo caricare dire 1000 linee alla volta ed elaborarlo?
Nikhil VJ

0

Grazie! Di recente mi sono convertito a Python 3 e sono stato frustrato dall'uso di readlines (0) per leggere file di grandi dimensioni. Questo ha risolto il problema. Ma per ottenere ogni riga, ho dovuto fare un paio di passaggi extra. Ogni riga era preceduta da una "b" che immagino fosse in formato binario. L'uso di "decodifica (utf-8)" lo ha cambiato ascii.

Quindi ho dovuto rimuovere un "= \ n" nel mezzo di ogni riga.

Quindi ho diviso le linee sulla nuova linea.

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)

Ecco il codice che inizia appena sopra i "dati di stampa" nel codice di Arohi.


0

Ho dimostrato un approccio di accesso casuale a livello di byte parallelo qui in questa altra domanda:

Ottenere il numero di righe in un file di testo senza readline

Alcune delle risposte già fornite sono belle e concise. Mi piacciono alcuni di loro. Ma dipende davvero da cosa vuoi fare con i dati contenuti nel file. Nel mio caso volevo solo contare le righe, il più velocemente possibile su file di testo di grandi dimensioni. Il mio codice può essere modificato per fare anche altre cose ovviamente, come qualsiasi altro codice.


0

La migliore soluzione che ho trovato al riguardo e l'ho provata su un file da 330 MB.

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

Dove line_length è il numero di caratteri in una singola riga. Ad esempio "abcd" ha la lunghezza della linea 4.

Ho aggiunto 2 in lunghezza per saltare il carattere '\ n' e passare al carattere successivo.


-1

Ciò può essere utile quando si desidera lavorare in parallelo e leggere solo blocchi di dati ma mantenerli puliti con nuove righe.

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

-10
f=open('filename','r').read()
f1=f.split('\n')
for i in range (len(f1)):
    do_something_with(f1[i])

spero che questo ti aiuti.


5
Questo non leggerebbe l'intero file in memoria? La domanda chiede esplicitamente come evitarlo, quindi questo non risponde alla domanda.
Paradosso di Fermi,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.