601 Stimmen

Wie liest man eine große Datei - Zeile für Zeile?

Ich möchte jede Zeile einer ganzen Datei durchlaufen. Eine Möglichkeit, dies zu tun, besteht darin, die gesamte Datei zu lesen, sie in einer Liste zu speichern und dann die betreffende Zeile zu durchlaufen. Diese Methode verbraucht viel Speicherplatz, daher suche ich nach einer Alternative.

Mein bisheriger Code:

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)

Das Ausführen dieses Codes führt zu einer Fehlermeldung: device active .

Irgendwelche Vorschläge?

Der Zweck ist die Berechnung der paarweisen String-Ähnlichkeit, d.h. für jede Zeile in der Datei möchte ich den Levenshtein-Abstand zu jeder anderen Zeile berechnen.

1378voto

Katriel Punkte 115208

Der korrekte, vollständig pythonische Weg, eine Datei zu lesen, ist der folgende:

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

El with Anweisung das Öffnen und Schließen der Datei, auch wenn im inneren Block eine Ausnahme ausgelöst wird. Die Anweisung for line in f behandelt das Dateiobjekt f als iterable, die automatisch gepufferte E/A und Speicherverwaltung verwendet, so dass Sie sich nicht um große Dateien kümmern müssen.

Es sollte einen - und vorzugsweise nur einen - offensichtlichen Weg geben, dies zu tun.

159voto

Srikar Appalaraju Punkte 68866

Zwei speichereffiziente Möglichkeiten in geordneter Reihenfolge (die erste ist die beste) -

  1. Verwendung von with - unterstützt ab Python 2.5 und höher
  2. Verwendung von yield wenn Sie wirklich selbst bestimmen wollen, wie viel Sie lesen

1. Verwendung von with

with ist der nette und effiziente pythonische Weg, große Dateien zu lesen. Vorteile - 1) Das Dateiobjekt wird automatisch geschlossen, nachdem es von with Ausführungsblock. 2) Ausnahmebehandlung innerhalb des with Block. 3) Speicher for Schleife iteriert durch die f Intern führt es gepufferte IO (zur Optimierung kostspieliger IO-Operationen) und Speicherverwaltung durch.

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

2. Verwendung von yield

Manchmal möchte man eine feinere Kontrolle darüber, wie viel in jeder Iteration gelesen werden soll. In diesem Fall verwenden Sie iter & Ertrag . Beachten Sie, dass man bei dieser Methode die Datei am Ende explizit schließen muss.

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 chunk in readInChunks(f):
    do_something(chunk)
f.close()

Fallstricke und der Vollständigkeit halber - Die folgenden Methoden sind nicht so gut oder nicht so elegant für das Lesen großer Dateien, aber lesen Sie bitte, um ein abgerundetes Verständnis zu bekommen.

In Python ist die gebräuchlichste Methode, Zeilen aus einer Datei zu lesen, die folgende:

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

Wenn dies jedoch geschieht, wird die readlines() Funktion (dasselbe gilt für read() Funktion) lädt die gesamte Datei in den Speicher und durchläuft sie dann. Ein etwas besserer Ansatz (die beiden erstgenannten Methoden sind die besten) für große Dateien ist die Verwendung der fileinput Modul, wie folgt:

import fileinput

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

die fileinput.input() Aufruf liest die Zeilen sequentiell, behält sie aber nicht im Speicher, nachdem sie gelesen wurden oder auch nur so, da file in Python ist iterierbar.

Referenzen

  1. Python mit Anweisung

39voto

Bob Stein Punkte 14259

Um Zeilenumbrüche zu entfernen:

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

Mit universelle Unterstützung von Zeilenumbrüchen werden alle Zeilen einer Textdatei scheinbar mit '\n' , unabhängig von den Terminatoren in der Datei, '\r' , '\n' , oder '\r\n' .

EDIT - Um die universelle Unterstützung von Zeilenumbrüchen festzulegen:

  • Python 2 unter Unix - open(file_path, mode='rU') - erforderlich [Dank @Dave ]
  • Python 2 unter Windows - open(file_path, mode='rU') - fakultativ
  • Python 3 - open(file_path, newline=None) - fakultativ

El newline wird nur in Python 3 unterstützt und ist standardmäßig auf None . Die mode Der Parameter ist standardmäßig auf 'r' in allen Fällen. Die Website U ist in Python 3 veraltet. In Python 2 unter Windows scheint ein anderer Mechanismus zur Übersetzung von \r\n a \n .

Dokumente:

Um native Leitungsabschlüsse zu erhalten:

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

Der Binärmodus kann die Datei immer noch in Zeilen zerlegen mit in . Jede Zeile wird mit den in der Datei vorhandenen Terminatoren versehen.

Dank an @katrielalex 's respuesta , Python's öffnen() doc, und iPython Experimente.

18voto

Simon Bergot Punkte 9940

Dies ist ein möglicher Weg, eine Datei in Python zu lesen:

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

es wird keine vollständige Liste zugewiesen. Es geht über die Zeilen hinweg.

13voto

Geoffrey Anderson Punkte 1353

Vorab einige Erläuterungen zu meinem Standpunkt. Codeschnipsel sind am Ende.

Wenn ich kann, verwende ich lieber ein Open-Source-Tool wie H2O, um parallele CSV-Dateien mit hoher Leistung zu lesen, aber dieses Tool hat nur einen begrenzten Funktionsumfang. Am Ende schreibe ich eine Menge Code, um Data Science Pipelines zu erstellen, bevor ich den H2O-Cluster für das eigentliche überwachte Lernen füttere.

Ich habe Dateien wie den 8 GB großen HIGGS-Datensatz aus dem UCI-Repository und sogar 40 GB große CSV-Dateien für datenwissenschaftliche Zwecke deutlich schneller gelesen, indem ich mit dem Pool-Objekt und der Map-Funktion der Multiprocessing-Bibliothek viel Parallelität hinzugefügt habe. So erfordert beispielsweise das Clustering mit Nearest Neighbour Searches sowie DBSCAN- und Markov-Clustering-Algorithmen eine gewisse Finesse bei der parallelen Programmierung, um einige schwierige Speicher- und Wanduhrzeitprobleme zu umgehen.

Normalerweise zerlege ich die Datei zeilenweise in Teile, indem ich zuerst die Gnu-Tools verwende, und dann werden sie alle mit glob-filemask gefunden und parallel im Python-Programm gelesen. Ich verwende in der Regel etwa 1000+ Teildateien. Diese Tricks helfen ungemein bei der Verarbeitungsgeschwindigkeit und der Speicherbegrenzung.

Der Pandas dataframe.read_csv ist single threaded, so dass Sie diese Tricks anwenden können, um Pandas schneller zu machen, indem Sie eine map() für die parallele Ausführung ausführen. Sie können htop verwenden, um zu sehen, dass bei der einfachen sequenziellen Ausführung von pandas dataframe.read_csv 100 % CPU-Leistung auf nur einem Kern der eigentliche Engpass in pd.read_csv ist, nicht die Festplatte.

Ich sollte hinzufügen, dass ich eine SSD am schnellen Grafikkartenbus verwende, keine Spinning HD am SATA6-Bus, plus 16 CPU-Kerne.

Eine weitere Technik, die sich in einigen Anwendungen bewährt hat, ist das parallele Lesen von CSV-Dateien innerhalb einer riesigen Datei, wobei jeder Worker an einem anderen Offset in der Datei beginnt, anstatt eine große Datei in viele Teildateien aufzuteilen. Verwenden Sie Python's file seek() und tell() in jedem parallelen Worker, um die große Textdatei in Streifen zu lesen, an verschiedenen Byte-Offset-Start-Byte und End-Byte-Positionen in der großen Datei, alle zur gleichen Zeit, gleichzeitig. Sie können einen Regex findall auf die Bytes anwenden und die Anzahl der Zeilenumbrüche zurückgeben. Dies ist eine Teilsumme. Zum Schluss werden die Teilsummen addiert, um die Gesamtsumme zu erhalten, wenn die Map-Funktion zurückkehrt, nachdem die Arbeiter fertig sind.

Nachfolgend finden Sie einige Beispiel-Benchmarks, die den Trick des parallelen Byte-Offsets verwenden:

Ich verwende 2 Dateien: HIGGS.csv ist 8 GB groß. Sie stammt aus dem UCI-Repository für maschinelles Lernen. all_bin.csv ist 40,4 GB groß und stammt aus meinem aktuellen Projekt. Ich verwende 2 Programme: Das GNU wc-Programm, das mit Linux geliefert wird, und das reine Python-Programm fastread.py, das ich entwickelt habe.

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

Das sind 4,5 GB/s bzw. 45 Gb/s, eine Geschwindigkeit, mit der Dateien geschlürft werden. Das ist keine rotierende Festplatte, mein Freund. Es handelt sich um eine Samsung Pro 950 SSD.

Unten sehen Sie den Geschwindigkeits-Benchmark für dieselbe Datei, die von gnu wc, einem rein in C kompilierten Programm, gezählt wird.

Cool ist, dass mein reines Python-Programm in diesem Fall im Wesentlichen mit der Geschwindigkeit des mit gnu wc kompilierten C-Programms mithalten kann. Python ist interpretiert, aber C ist kompiliert, also ist dies eine ziemlich interessante Leistung in Bezug auf die Geschwindigkeit, ich denke, Sie werden zustimmen. Natürlich muss wc wirklich zu einem parallelen Programm geändert werden, und dann würde es mein Python-Programm wirklich von den Socken hauen. Aber so wie es heute aussieht, ist gnu wc nur ein sequentielles Programm. Man tut, was man kann, und Python kann heute parallel arbeiten. Das Kompilieren von Cython könnte mir vielleicht helfen (für ein anderes Mal). Auch memory mapped files wurden noch nicht erforscht.

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

Schlussfolgerung: Die Geschwindigkeit ist für ein reines Python-Programm im Vergleich zu einem C-Programm gut. Es ist jedoch nicht gut genug, um das reine Python-Programm dem C-Programm vorzuziehen, zumindest für den Zweck der Zeilenzählung. Im Allgemeinen kann die Technik auch für andere Dateiverarbeitungen verwendet werden, so dass dieser Python-Code immer noch gut ist.

Frage: Wird die Geschwindigkeit erhöht, wenn die Regex nur einmal kompiliert und an alle Worker übergeben wird? Antwort: Ja: Das Vorkompilieren von Regex hilft in dieser Anwendung NICHT. Ich nehme an, der Grund ist, dass der Overhead der Prozessserialisierung und -erstellung für alle Worker dominiert.

Noch eine Sache. Ist das parallele Lesen von CSV-Dateien überhaupt hilfreich? Ist die Festplatte der Engpass, oder ist es die CPU? Viele so genannte Top-Rating-Antworten auf Stackoverflow enthalten die gängige Entwicklerweisheit, dass man nur einen Thread zum Lesen einer Datei braucht, das Beste, was man tun kann. Sind sie sich aber sicher?

Finden wir es heraus:

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 ja, das tut es. Das parallele Lesen von Dateien funktioniert sehr gut. Na also, geht doch!

Ps. Falls einige von euch wissen wollen, was passiert, wenn der balanceFactor bei Verwendung eines einzelnen Worker-Prozesses 2 ist? Nun, das ist furchtbar:

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

Die wichtigsten Teile des fastread.py Py-Programms:

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

Die Def für PartitionDataToWorkers ist ein ganz normaler sequentieller Code. Ich habe ihn weggelassen, für den Fall, dass jemand etwas Übung in paralleler Programmierung haben möchte. Die schwierigeren Teile habe ich kostenlos abgegeben: den getesteten und funktionierenden parallelen Code, damit Sie etwas lernen können.

Dank an: Das Open-Source-Projekt H2O von Arno und Cliff und den H2O-Mitarbeitern für ihre großartige Software und die Anleitungsvideos, die mich zu dem oben gezeigten hochleistungsfähigen parallelen Byte-Offset-Reader in Python inspiriert haben. H2O liest parallele Dateien mit Java, kann von Python und R-Programmen aufgerufen werden und ist wahnsinnig schnell, schneller als alles andere auf der Welt beim Lesen großer CSV-Dateien.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X