Come leggere un file di grandi dimensioni, riga per riga?


536

Voglio iterare su ogni riga di un intero file. Un modo per farlo è leggere l'intero file, salvarlo in un elenco, quindi andare oltre la linea di interesse. Questo metodo utilizza molta memoria, quindi sto cercando un'alternativa.

Il mio codice finora:

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)

L'esecuzione di questo codice dà un messaggio di errore: device active.

Eventuali suggerimenti?

Lo scopo è calcolare la somiglianza delle stringhe in coppia, ovvero per ogni riga del file, voglio calcolare la distanza di Levenshtein con ogni altra riga.


4
Perché è necessario leggere nuovamente l'intero file per ogni riga? Forse se dicessi cosa stai cercando di realizzare, qualcuno potrebbe essere in grado di suggerire un approccio migliore.
JJJ,

Risposte:


1269

Il modo corretto e completamente Pythonic per leggere un file è il seguente:

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

L' withistruzione gestisce l'apertura e la chiusura del file, anche se viene sollevata un'eccezione nel blocco interno. I for line in ftratta l'oggetto File fcome un iterabile, che automaticamente utilizza buffer di I / O e la gestione della memoria in modo da non dovete preoccuparvi di file di grandi dimensioni.

Dovrebbe esserci un modo - e preferibilmente solo uno - ovvio per farlo.


14
sì, questa è la versione migliore con Python 2.6 e versioni successive
Simon Bergot,

3
Personalmente preferisco generatori e coroutine per gestire pipeline di dati.
jldupont,

4
quale sarebbe la migliore strategia se un file è un file di testo enorme ma con una riga e l'idea è di elaborare le parole?
mfcabrera,

4
Qualcuno potrebbe spiegare come for line in f:funziona? Voglio dire, come è possibile iterare sull'oggetto file?
Hawcks

11
Se si scorre su un oggetto, Python cerca nell'elenco dei metodi oggetto uno speciale chiamato __iter__, che gli dice cosa fare. Gli oggetti file definiscono questo metodo speciale per restituire un iteratore sopra le righe. (Circa.)
Katriel,

130

Due modi di memoria efficienti in ordine di classificazione (il primo è il migliore) -

  1. uso di with- supportato da Python 2.5 e versioni successive
  2. uso di yieldse vuoi davvero avere il controllo su quanto leggere

1. uso di with

withè il modo piacevole ed efficiente di pitone per leggere file di grandi dimensioni. vantaggi: 1) l'oggetto file viene automaticamente chiuso dopo essere uscito dal withblocco di esecuzione. 2) gestione delle eccezioni all'interno del withblocco. 3) il forloop di memoria scorre l'argomento ffile riga per riga. internamente esegue il buffering IO (ottimizzato su costose operazioni IO) e la gestione della memoria.

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

2. uso di yield

A volte si potrebbe desiderare un controllo più approfondito su quanto leggere in ogni iterazione. In tal caso, utilizzare iter & yield . Nota con questo metodo è necessario chiudere esplicitamente il file alla fine.

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()

Insidie ​​e per completezza : i metodi di seguito non sono così buoni o meno eleganti per la lettura di file di grandi dimensioni, ma si prega di leggere per ottenere una comprensione arrotondata.

In Python, il modo più comune per leggere le righe da un file è quello di fare quanto segue:

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

Al termine, tuttavia, la readlines()funzione (lo stesso vale per la read()funzione) carica l'intero file in memoria, quindi scorre su di esso. Un approccio leggermente migliore (i primi due metodi citati sono i migliori) per file di grandi dimensioni consiste nell'utilizzare il fileinputmodulo, come segue:

import fileinput

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

la fileinput.input()chiamata legge le righe in sequenza, ma non le mantiene in memoria dopo che sono state lette o anche semplicemente, poiché filein Python è iterabile.

Riferimenti

  1. Python con dichiarazione

9
-1 In pratica non è mai una buona idea for line in open(...).readlines(): <do stuff>. Perchè vorresti?! Hai appena perso tutti i vantaggi dell'intelligente iteratore bufferizzato di Python per nessun beneficio.
Katriel,

5
@Srikar: c'è un tempo e un posto per dare tutte le possibili soluzioni a un problema; insegnare a un principiante come fare l'input di file non è vero. Avere la risposta corretta seppellita in fondo a un lungo post pieno di risposte sbagliate non è un buon insegnamento.
Katriel,

6
@Srikar: potresti migliorare notevolmente il tuo post mettendo la strada giusta in alto, quindi menzionando readlinese spiegando perché non è una buona cosa da fare (perché legge il file in memoria), quindi spiegando cosa fa il fileinputmodulo e perché potrebbe essere utile usarlo con gli altri metodi, spiegando poi come frammentare il file migliora l'IO e dare un esempio della funzione di chunking (ma menzionando che Python lo fa già per te, quindi non è necessario). Ma dare solo cinque modi per risolvere un semplice problema, quattro dei quali sono sbagliati in questo caso, non va bene.
Katriel,

2
Qualunque cosa tu aggiunga per completezza, aggiungila per ultima, non prima. Prima mostra il modo corretto.
m000,

6
@katrielalex ha rivisitato la mia risposta e ha scoperto che merita una ristrutturazione. Vedo come la risposta precedente potrebbe causare confusione. Speriamo che ciò chiarisca per i futuri utenti.
Srikar Appalaraju,

37

Per eliminare le nuove righe:

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

Con il supporto universale ai fine riga tutte le linee di file di testo sembreranno essere terminato con '\n', quali che siano le terminazioni nel file, '\r', '\n', o '\r\n'.

EDIT - Per specificare il supporto universale newline:

  • Python 2 su Unix - open(file_path, mode='rU')- richiesto [grazie @Dave ]
  • Python 2 su Windows - open(file_path, mode='rU')- opzionale
  • Python 3 - open(file_path, newline=None)- opzionale

Il newlineparametro è supportato solo in Python 3 e per impostazione predefinita è None. Il modeparametro predefinito è 'r'in tutti i casi. Il Uè deprecato in Python 3. In Python 2 su Windows qualche altro meccanismo sembra di tradurre \r\na \n.

Documenti:

Per conservare i terminatori di linea nativi:

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

La modalità binaria può ancora analizzare il file in righe con in. Ogni riga avrà qualunque terminatore nel file.

Grazie alla @katrielalex 's risposta , di Python open () doc, e ipython esperimenti.


1
Su Python 2.7 ho dovuto open(file_path, 'rU')abilitare le nuove linee universali.
Dave,

17

questo è un modo possibile di leggere un file in Python:

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

non alloca un elenco completo. Ripete le righe.


2
Mentre funziona, non è sicuramente il modo canonico. Il modo canonico è usare un wrapper di contesto, come with open(input_file) as f:. Questo ti f.close()fa risparmiare e ti assicura di non dimenticare accidentalmente di chiuderlo. Previene perdite di memoria e tutto il resto, molto importante durante la lettura dei file.
Albero

1
Come ha detto @Mast, questo non è il modo canonico, quindi votate a favore.
azuax,

12

Qualche contesto in anticipo su da dove vengo. Gli snippet di codice sono alla fine.

Quando posso, preferisco usare uno strumento open source come H2O per fare letture di file CSV parallele ad altissime prestazioni, ma questo strumento è limitato nel set di funzionalità. Finisco per scrivere un sacco di codice per creare pipeline di data science prima di alimentare il cluster H2O per l'apprendimento supervisionato.

Ho letto file come set di dati HIGGS da 8 GB dal repository UCI e persino file CSV da 40 GB per scopi di data science molto più velocemente aggiungendo un sacco di parallelismo con l'oggetto pool della libreria a elaborazione multipla e la funzione mappa. Ad esempio, il clustering con le ricerche dei vicini più vicini e anche gli algoritmi di clustering DBSCAN e Markov richiedono un po 'di finezza di programmazione parallela per aggirare alcuni problemi di memoria e tempo di clock davvero impegnativi.

Di solito mi piace spezzare il file in ordine di riga in parti usando prima gli strumenti gnu e poi glob-file maschera tutti per trovarli e leggerli in parallelo nel programma python. Uso qualcosa come 1000+ file parziali comunemente. Fare questi trucchi aiuta immensamente con velocità di elaborazione e limiti di memoria.

Panda dataframe.read_csv è a thread singolo, quindi puoi fare questi trucchi per rendere i panda abbastanza più veloci eseguendo una mappa () per l'esecuzione parallela. Puoi usare htop per vedere che con i semplici vecchi panda sequenziali dataframe.read_csv, 100% cpu su un solo core è l'effettivo collo di bottiglia in pd.read_csv, non il disco.

Dovrei aggiungere che sto usando un SSD sul bus veloce della scheda video, non un HD rotante sul bus SATA6, più 16 core della CPU.

Inoltre, un'altra tecnica che ho scoperto funziona alla grande in alcune applicazioni è la lettura parallela di file CSV all'interno di un unico file gigante, avviando ogni lavoratore con offset diverso nel file, anziché suddividere un grande file in molti file di parti. Utilizzare il file seek () e tell () di python in ogni lavoratore parallelo per leggere il file di testo grande in strip, in posizioni di byte di inizio e di byte di fine byte diverse nel file grande, contemporaneamente allo stesso tempo. È possibile eseguire una regex findall sui byte e restituire il conteggio degli avanzamenti di riga. Questa è una somma parziale. Infine sommare le somme parziali per ottenere la somma globale quando la funzione della mappa ritorna dopo che i lavoratori hanno finito.

Di seguito sono riportati alcuni esempi di benchmark che utilizzano il trucco dell'offset di byte parallelo:

Uso 2 file: HIGGS.csv ha 8 GB. Viene dal repository di apprendimento automatico dell'UCI. all_bin .csv è 40,4 GB e proviene dal mio progetto attuale. Uso 2 programmi: il programma GNU wc fornito con Linux e il programma pastron fastread.py puro che ho sviluppato.

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

Sono circa 4,5 GB / s, o 45 Gb / s, la velocità del file slurping. Non è un disco rigido rotante, amico mio. Questo è in realtà un SSD Samsung Pro 950.

Di seguito è riportato il benchmark di velocità per lo stesso file che viene contato in linea da gnu wc, un programma compilato in C puro.

Ciò che è bello è che puoi vedere che il mio programma Python puro ha sostanzialmente eguagliato la velocità del programma C compilato da gnu wc in questo caso. Python è interpretato ma C è compilato, quindi questa è una prodezza piuttosto interessante di velocità, penso che saresti d'accordo. Ovviamente, il wc ha davvero bisogno di essere cambiato in un programma parallelo, e poi avrebbe davvero battuto le calze dal mio programma Python. Ma com'è oggi, gnu wc è solo un programma sequenziale. Fai ciò che puoi e Python può fare parallelamente oggi. La compilazione di Cython potrebbe essere in grado di aiutarmi (per qualche altra volta). Anche i file mappati in memoria non sono stati ancora esplorati.

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

Conclusione: la velocità è buona per un programma Python puro rispetto a un programma C. Tuttavia, non è abbastanza buono per usare il programma Python puro sul programma C, almeno per scopi di lincounting. Generalmente la tecnica può essere utilizzata per l'elaborazione di altri file, quindi questo codice Python è ancora buono.

Domanda: Compilare il regex solo una volta e passarlo a tutti i lavoratori migliorerà la velocità? Risposta: La pre-compilazione Regex NON aiuta in questa applicazione. Suppongo che la ragione sia che il sovraccarico della serializzazione dei processi e della creazione per tutti i lavoratori sta dominando.

Un'altra cosa. La lettura parallela di file CSV aiuta anche? Il disco è il collo di bottiglia o è la CPU? Molte cosiddette risposte più votate su StackOverflow contengono la comune saggezza degli sviluppatori secondo cui è necessario un solo thread per leggere un file, meglio che puoi fare, dicono. Sono sicuri, però?

Scopriamolo:

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 sì, sì lo fa. La lettura di file paralleli funziona abbastanza bene. Bene ecco qua!

Ps. Nel caso in cui qualcuno di voi volesse sapere, cosa succede se balanceFactor era 2 quando si utilizza un processo a singolo lavoratore? Bene, è orribile:

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

Parti chiave del programma pastron 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 definizione di PartitionDataToWorkers è solo un normale codice sequenziale. L'ho lasciato fuori nel caso in cui qualcun altro voglia fare pratica su come sia la programmazione parallela. Ho regalato gratuitamente le parti più dure: il codice parallelo testato e funzionante, per il tuo vantaggio di apprendimento.

Grazie a: Il progetto H2O open-source, di Arno e Cliff e dello staff di H2O per i loro fantastici software e video didattici, mi ha fornito l'ispirazione per questo lettore di offset a byte parallelo ad alte prestazioni in puro pitone, come mostrato sopra. H2O esegue la lettura di file paralleli utilizzando Java, è richiamabile da programmi Python e R ed è pazzo veloce, più veloce di qualsiasi altra cosa al mondo a leggere file CSV di grandi dimensioni.


I pezzi paralleli sono fondamentalmente questo. Inoltre, mi aspetto che SSD e Flash siano gli unici dispositivi di archiviazione compatibili con questa tecnica. È improbabile che Spinning HD sia compatibile.
Geoffrey Anderson,

1
Come hai tenuto conto dei file del disco di memorizzazione nella cache del sistema operativo?
JamesThomasMoon1979

5

Katrielalex ha fornito il modo di aprire e leggere un file.

Tuttavia, il modo in cui procede l'algoritmo legge l'intero file per ogni riga del file. Ciò significa che la quantità complessiva di lettura di un file - e calcolo della distanza di Levenshtein - verrà eseguita N * N se N è la quantità di righe nel file. Dato che sei preoccupato per le dimensioni del file e non vuoi tenerlo in memoria, sono preoccupato per il conseguente runtime quadratico . Il tuo algoritmo appartiene alla classe di algoritmi O (n ^ 2) che spesso può essere migliorata con la specializzazione.

Ho il sospetto che tu sappia già il compromesso della memoria rispetto al runtime qui, ma forse vorresti indagare se esiste un modo efficace per calcolare più distanze di Levenshtein in parallelo. In tal caso, sarebbe interessante condividere la tua soluzione qui.

Quante linee hanno i tuoi file e su quale tipo di macchina (potenza mem & cpu) deve funzionare il tuo algoritmo e qual è il runtime tollerato?

Il codice sarebbe simile a:

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)

Ma le domande sono: come si memorizzano le distanze (matrice?) E si può ottenere un vantaggio dalla preparazione, ad esempio, della linea esterna per l'elaborazione, o dalla memorizzazione nella cache di alcuni risultati intermedi per il riutilizzo.


Il mio punto è che questo post non contiene una risposta alla domanda, solo alcune altre domande! IMO sarebbe più adatto come commento.
Katriel,

1
@katriealex: Err. Strano. Hai visto i loop nidificati, espandendo la tua risposta per adattarla alla domanda reale? Posso rimuovere le mie domande qui dalla mia risposta, e ci sono ancora abbastanza contenuti da giustificare fornendo questa come una risposta - anche se parziale -. Potrei anche accettare se modifichi la tua risposta per includere l'esempio di ciclo nidificato - che è stato esplicitamente posto dalla domanda - e quindi posso rimuovere la mia risposta felicemente. Ma un downvote è qualcosa che non capisco affatto.
cfi,

Giusto; Non vedo davvero la dimostrazione dei loop nidificati come risposta alla domanda, ma immagino che sia piuttosto indirizzata ai principianti. Downvote rimosso.
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
  • Apri il tuo file per la lettura (r)
  • Leggi l'intero file e salva ogni riga in un elenco (testo)
  • Scorrere l'elenco stampando ciascuna riga.

Se ad esempio vuoi controllare una linea specifica per una lunghezza superiore a 10, lavora con ciò che hai già a disposizione.

for line in text:
    if len(line) > 10:
        print line

1
Non è il migliore per questa domanda, ma questo codice è utile soprattutto nel caso in cui quello che stai cercando è "slurping" (leggere l'intero file in una sola volta). Quello era il mio caso e Google mi ha portato qui. +1. Inoltre, per atomicità, o se si esegue un'elaborazione che richiede tempo nel ciclo, potrebbe finire più velocemente per leggere l'intero file
ntg

1
Inoltre, ho migliorato un po 'il codice: 1. dopo non è necessario chiudere con: ( docs.python.org/2/tutorial/inputoutput.html , cercare "È buona norma utilizzare la parola chiave with ...") 2 Il testo può essere elaborato dopo la lettura del file (a parte con il ciclo ....)
ntg

2

Dalla documentazione di Python per fileinput .input ():

Ciò scorre tra le righe di tutti i file elencati sys.argv[1:], per impostazione predefinita sys.stdinse l'elenco è vuoto

inoltre, la definizione della funzione è:

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

leggendo tra le righe, questo mi dice che filespuò essere un elenco in modo da poter avere qualcosa di simile:

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

Vedi qui per maggiori informazioni


2

Consiglio vivamente di non utilizzare il caricamento del file predefinito poiché è terribilmente lento. Dovresti esaminare le funzioni numpy e le funzioni IOpro (ad esempio numpy.loadtxt ()).

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

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

Quindi puoi suddividere l'operazione a coppie in blocchi:

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) 

È quasi sempre molto più veloce caricare i dati in blocchi e quindi eseguire operazioni a matrice su di esso piuttosto che farlo elemento per elemento !!


0

Hai bisogno di leggere spesso un file di grandi dimensioni dall'ultima posizione in lettura?

Ho creato uno script usato per tagliare un file access.log di Apache più volte al giorno. Quindi avevo bisogno di impostare un cursore di posizione sull'ultima riga analizzata durante l'ultima esecuzione . A tal fine, ho usato file.seek()e file.seek()metodi che consentono la memorizzazione del cursore nel file.

Il mio codice:

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()))

-2

Il modo migliore per leggere file di grandi dimensioni, riga per riga, è utilizzare la funzione di enumerazione di 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

3
Perché usare enumerate è meglio? L'unico vantaggio rispetto alla risposta accettata è che ottieni un indice, di cui OP non ha bisogno e stai rendendo il codice meno leggibile.
fuyas,
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.