Wie kann ich große Textdateien in Python Zeile für Zeile lesen, ohne sie in den Speicher zu laden?

238

Ich muss eine große Datei Zeile für Zeile lesen. Nehmen wir an, die Datei hat mehr als 5 GB und ich muss jede Zeile lesen, aber ich möchte sie natürlich nicht verwenden, readlines()da dadurch eine sehr große Liste im Speicher erstellt wird.

Wie funktioniert der folgende Code für diesen Fall? Liest xreadlinessich eins nach dem anderen ins Gedächtnis? Wird der Generatorausdruck benötigt?

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

f.next()  

Und was kann ich tun, um dies in umgekehrter Reihenfolge zu lesen, genau wie der Linux- tailBefehl?

Ich fand:

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

und

" Python Kopf, Schwanz und rückwärts gelesen durch Zeilen einer Textdatei "

Beides hat sehr gut funktioniert!

Bruno Rocha - Rochacbruno
quelle
Und was kann ich tun, um dies vom Schwanz zu lesen? Zeile für Zeile, beginnend in der letzten Zeile.
Bruno Rocha - Rochacbruno
Dies sollte eine separate Frage sein
cmcginty

Antworten:

308

Ich habe diese Antwort gegeben, weil Keith's die Datei zwar kurz, aber nicht explizit schließt

with open("log.txt") as infile:
    for line in infile:
        do_something_with(line)
John La Rooy
quelle
30
Die Frage ist immer noch, ob "for line in infile" meine 5 GB Zeilen in den Speicher laden wird. und, wie kann ich vom Schwanz lesen?
Bruno Rocha - Rochacbruno
66
@rochacbruno, es liest jeweils nur eine Zeile. Wenn die nächste Zeile gelesen wird, wird die vorherige Zeile Müll gesammelt, es sei denn, Sie haben einen Verweis darauf woanders gespeichert
John La Rooy
1
@rochacbruno, Das Lesen der Zeilen in umgekehrter Reihenfolge ist leider nicht so einfach. Im Allgemeinen möchten Sie vom Ende der Datei in vernünftigen Blöcken (z. B. Kilobyte bis Megabyte) lesen und in Zeilenumbrüche aufteilen (oder was auch immer das Zeilenendezeichen auf Ihrer Plattform ist)
John La Rooy
4
Vielen Dank! Ich fand die Schwanzlösung stackoverflow.com/questions/5896079/…
Bruno Rocha - rochacbruno
1
@bawejakunal, Meinst du, wenn eine Zeile zu lang ist, um sofort in den Speicher geladen zu werden? Das ist ungewöhnlich für eine Textdatei . Anstatt eine forSchleife zu verwenden, die über die Zeilen iteriert, können Sie Blöcke chunk = infile.read(chunksize)mit begrenzter Größe unabhängig von ihrem Inhalt lesen. Sie müssen selbst in den Chunks nach Zeilenumbrüchen suchen.
John La Rooy
59

Sie müssen lediglich das Dateiobjekt als Iterator verwenden.

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

Noch besser ist die Verwendung des Kontextmanagers in neueren Python-Versionen.

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

Dadurch wird auch die Datei automatisch geschlossen.

Keith
quelle
2
Das lädt nicht die ganze Datei in den Speicher?
Bruno Rocha - Rochacbruno
17

Ein Ansatz der alten Schule:

fh = open(file_name, 'rt')
line = fh.readline()
while line:
    # do stuff with line
    line = fh.readline()
fh.close()
PTBNL
quelle
2
kleine Bemerkung: Aus Sicherheitsgründen wird empfohlen, die Anweisung 'with' zu verwenden, in Ihrem Fall "with open (Dateiname, 'rt') als fh:"
prokher
16
@prokher: Ja, aber ich habe das "alte Schule" genannt.
PTBNL
15

Verwenden Sie stattdessen besser einen Iterator. Relevant: http://docs.python.org/library/fileinput.html

Aus den Dokumenten:

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

Dadurch wird vermieden, dass die gesamte Datei auf einmal in den Speicher kopiert wird.

Mikola
quelle
Obwohl die Dokumente das Snippet als "typische Verwendung" anzeigen, ruft die Verwendung des Snippets die close()Methode des zurückgegebenen FileInputKlassenobjekts nicht auf, wenn die Schleife beendet ist. Daher würde ich es auf diese Weise vermeiden. In Python 3.2 haben sie es endlich fileinputmit dem Kontextmanager-Protokoll kompatibel gemacht , das dieses Problem behebt (aber der Code würde immer noch nicht so geschrieben, wie gezeigt).
Martineau
7

Folgendes tun Sie, wenn die Datei keine Zeilenumbrüche enthält:

with open('large_text.txt') as f:
  while True:
    c = f.read(1024)
    if not c:
      break
    print(c)
Ariel Cabib
quelle
Während ich diese Methode mag, laufen Sie Gefahr, dass die Zeilen in Ihrem Text in Abschnitte unterteilt werden. Ich habe das persönlich gesehen, was bedeutet, dass ich, wenn Sie wie ich nach sstring in der Datei suchen, einige vermissen würde, weil die Zeile, in der sie sich befanden, in Stücke gebrochen war. Gibt es eine Möglichkeit, dies zu umgehen? Das Verwenden von Readlines funktionierte nicht gut, da ich
edo101
6

Bitte versuchen Sie dies:

with open('filename','r',buffering=100000) as f:
    for line in f:
        print line
jyoti das
quelle
bitte erkläre?
Nikhil VJ
3
Aus Pythons offiziellen docmunets: link Das optionale Pufferargument gibt die gewünschte Puffergröße der Datei an: 0 bedeutet ungepuffert, 1 bedeutet zeilengepuffert, jeder andere positive Wert bedeutet, einen Puffer von (ungefähr) dieser Größe (in Bytes) zu verwenden. Eine negative Pufferung bedeutet, den Systemstandard zu verwenden, der normalerweise für tty-Geräte zeilengepuffert und für andere Dateien vollständig gepuffert ist. Wenn nicht angegeben, wird die Systemvorgabe verwendet
jyoti das
In meinem Fall wurde mein Tag mit> ~ 4 GB Dateien mit zwei Dateihandlern (einer gelesen, der andere geschrieben) gespeichert. Python hing und jetzt ist es in Ordnung! Vielen Dank.
Xelt
@jyotidas Während ich diese Methode mag, laufen Sie Gefahr, dass die Linie in Ihrem Text in Stücke gebrochen wird. Ich habe das persönlich gesehen, was bedeutet, dass ich, wenn Sie wie ich nach sstring in der Datei suchen, einige vermissen würde, weil die Zeile, in der sie sich befanden, in Stücke gebrochen war. Gibt es eine Möglichkeit, dies zu umgehen? Das Verwenden von Readlines funktionierte nicht gut, da ich Fehlzählungen bekam
edo101
3

Ich konnte nicht glauben, dass es so einfach sein könnte, wie es die Antwort von @ john-la-rooy schien. Also habe ich den cpBefehl durch zeilenweises Lesen und Schreiben neu erstellt. Es ist verrückt schnell.

#!/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
quelle
HINWEIS: Da Python readlineZeilenenden standardisiert, hat dies den Nebeneffekt, dass Dokumente mit DOS-Zeilenenden von \r\nin Unix-Zeilenenden von konvertiert werden \n. Mein ganzer Grund für die Suche nach diesem Thema war, dass ich eine Protokolldatei konvertieren musste, die ein Durcheinander von Zeilenenden empfängt (weil der Entwickler blind verschiedene .NET-Bibliotheken verwendet hat). Ich war schockiert, als ich feststellte, dass ich nach meinem ersten Geschwindigkeitstest nicht zurück und rstripdie Linien gehen musste. Es war schon perfekt!
Bruno Bronosky
2

Das Blaze- Projekt hat in den letzten 6 Jahren einen langen Weg zurückgelegt. Es verfügt über eine einfache API, die eine nützliche Teilmenge der Pandas-Funktionen abdeckt.

dask.dataframe kümmert sich intern um das Chunking, unterstützt viele parallelisierbare Vorgänge und ermöglicht das einfache Exportieren von Slices zurück in Pandas für In-Memory-Vorgänge.

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
quelle
2

Hier ist der Code zum Laden von Textdateien beliebiger Größe, ohne Speicherprobleme zu verursachen. Es unterstützt Dateien mit einer Größe von Gigabyte

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

Laden Sie die Datei data_loading_utils.py herunter und importieren Sie sie in Ihren Code

Verwendung

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)

Die Methode process_lines ist die Rückruffunktion. Es wird für alle Zeilen aufgerufen, wobei Parameterdaten jeweils eine einzelne Zeile der Datei darstellen.

Sie können die Variable CHUNK_SIZE abhängig von der Hardware-Konfiguration Ihrer Maschine konfigurieren.

Iyvin Jose
quelle
Während ich diese Methode mag, laufen Sie Gefahr, dass die Zeilen in Ihrem Text in Abschnitte unterteilt werden. Ich habe das persönlich gesehen, was bedeutet, dass ich, wenn Sie wie ich nach sstring in der Datei suchen, einige vermissen würde, weil die Zeile, in der sie sich befanden, in Stücke gebrochen war. Gibt es eine Möglichkeit, dies zu umgehen? Das Verwenden von Readlines funktionierte nicht gut, da ich Fehlzählungen bekam
edo101
0

Wie wäre es damit? Teilen Sie Ihre Datei in Blöcke und lesen Sie sie dann Zeile für Zeile, da Ihr Betriebssystem beim Lesen einer Datei die nächste Zeile zwischenspeichert. Wenn Sie die Datei Zeile für Zeile lesen, nutzen Sie die zwischengespeicherten Informationen nicht effizient.

Teilen Sie stattdessen die Datei in Blöcke und laden Sie den gesamten Block in den Speicher. Führen Sie dann Ihre Verarbeitung durch.

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
quelle
Das sieht vielversprechend aus. Wird dies durch Bytes oder durch Zeilen geladen? Ich habe Angst, dass Zeilen unterbrochen werden, wenn es sich um Bytes handelt. Wie können wir beispielsweise 1000 Zeilen gleichzeitig laden und das verarbeiten?
Nikhil VJ
0

Danke dir! Ich habe kürzlich auf Python 3 konvertiert und war frustriert, weil ich Readlines (0) zum Lesen großer Dateien verwendet habe. Dies löste das Problem. Aber um jede Zeile zu bekommen, musste ich ein paar zusätzliche Schritte machen. Vor jeder Zeile stand ein "b", das vermutlich im Binärformat war. Mit "decode (utf-8)" wurde es ascii geändert.

Dann musste ich ein "= \ n" in der Mitte jeder Zeile entfernen.

Dann habe ich die Zeilen an der neuen Zeile geteilt.

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)

Hier ist der Code, der direkt über "Daten drucken" in Arohis Code beginnt.

John Haynes
quelle
0

In dieser anderen Frage habe ich hier einen Parallelzugriffsansatz auf Byte-Ebene demonstriert:

Abrufen der Anzahl der Zeilen in einer Textdatei ohne Readlines

Einige der bereits gegebenen Antworten sind nett und prägnant. Ich mag einige von ihnen. Aber es hängt wirklich davon ab, was Sie mit den Daten in der Datei machen möchten. In meinem Fall wollte ich nur Zeilen zählen, so schnell wie möglich bei großen Textdateien. Mein Code kann natürlich auch geändert werden, um andere Dinge zu tun, wie jeden Code.

Geoffrey Anderson
quelle
0

Die beste Lösung, die ich in dieser Hinsicht gefunden habe, und ich habe es mit einer 330-MB-Datei versucht.

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

Wobei line_length die Anzahl der Zeichen in einer einzelnen Zeile ist. Zum Beispiel hat "abcd" die Zeilenlänge 4.

Ich habe 2 Zeilen hinzugefügt, um das Zeichen '\ n' zu überspringen und zum nächsten Zeichen zu wechseln.

Ali Sajjad
quelle
-1

Dies kann nützlich sein, wenn Sie parallel arbeiten und nur Datenblöcke lesen möchten, diese aber mit neuen Zeilen sauber halten möchten.

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
Adam
quelle
-10
f=open('filename','r').read()
f1=f.split('\n')
for i in range (len(f1)):
    do_something_with(f1[i])

hoffe das hilft.

Sainik Kr Mahata
quelle
5
Würde dies nicht die gesamte Datei im Speicher lesen? In der Frage wird explizit gefragt, wie dies vermieden werden kann. Daher wird die Frage dadurch nicht beantwortet.
Fermi Paradox