381 Stimmen

Lazy Methode für das Lesen von großen Datei in Python?

Ich habe eine sehr große Datei von 4 GB, und wenn ich versuche, sie zu lesen, bleibt mein Computer hängen. Ich möchte sie also Stück für Stück lesen und nach der Verarbeitung jedes Stücks das verarbeitete Stück in einer anderen Datei speichern und das nächste Stück lesen.

Gibt es eine Methode, um yield diese Stücke ?

Ich hätte gerne eine Faulenzermethode .

553voto

nosklo Punkte 204121

Um eine "Lazy Function" zu schreiben, verwenden Sie einfach yield :

def read_in_chunks(file_object, chunk_size=1024):
    """Lazy function (generator) to read a file piece by piece.
    Default chunk size: 1k."""
    while True:
        data = file_object.read(chunk_size)
        if not data:
            break
        yield data

with open('really_big_file.dat') as f:
    for piece in read_in_chunks(f):
        process_data(piece)

Eine andere Möglichkeit wäre die Verwendung von iter und eine Hilfsfunktion:

f = open('really_big_file.dat')
def read1k():
    return f.read(1024)

for piece in iter(read1k, ''):
    process_data(piece)

Wenn die Datei zeilenbasiert ist, ist das Datei-Objekt bereits ein träger Generator von Zeilen:

for line in open('really_big_file.dat'):
    process_data(line)

49voto

Anshul Punkte 459

file.readlines() nimmt ein optionales Größenargument auf, das die Anzahl der gelesenen Zeilen in den zurückgegebenen Zeilen annähernd angibt.

bigfile = open('bigfilename','r')
tmp_lines = bigfile.readlines(BUF_SIZE)
while tmp_lines:
    process([line for line in tmp_lines])
    tmp_lines = bigfile.readlines(BUF_SIZE)

48voto

user48678 Punkte 2173

Es gibt bereits viele gute Antworten, aber wenn Ihre gesamte Datei in einer einzigen Zeile steht und Sie trotzdem "Zeilen" (im Gegensatz zu Blöcken fester Größe) verarbeiten wollen, werden Ihnen diese Antworten nicht helfen.

In 99 % der Fälle ist es möglich, Dateien zeilenweise zu verarbeiten. Dann, wie in diesem Artikel vorgeschlagen Antwort können Sie das Dateiobjekt selbst als Lazy Generator verwenden:

with open('big.csv') as f:
    for line in f:
        process(line)

Es kann jedoch vorkommen, dass sehr große Dateien vorliegen, bei denen das Zeilentrennzeichen nicht '\n' (ein häufiger Fall ist '|' ).

  • Konvertieren '|' a '\n' vor der Verarbeitung nicht möglich ist, weil dadurch Felder, die legitimerweise enthalten sind, durcheinander gebracht werden können '\n' (z.B. freie Texteingabe durch den Benutzer).
  • Die Verwendung der csv-Bibliothek scheidet auch deshalb aus, weil zumindest in frühen Versionen der lib, es ist fest programmiert, die Eingabe zeilenweise zu lesen .

Für diese Art von Situationen habe ich das folgende Snippet erstellt [Aktualisiert im Mai 2021 für Python 3.8+]:

def rows(f, chunksize=1024, sep='|'):
    """
    Read a file where the row separator is '|' lazily.

    Usage:

    >>> with open('big.csv') as f:
    >>>     for r in rows(f):
    >>>         process(r)
    """
    row = ''
    while (chunk := f.read(chunksize)) != '':   # End of file
        while (i := chunk.find(sep)) != -1:     # No separator found
            yield row + chunk[:i]
            chunk = chunk[i+1:]
            row = ''
        row += chunk
    yield row

[Für ältere Versionen von Python]:

def rows(f, chunksize=1024, sep='|'):
    """
    Read a file where the row separator is '|' lazily.

    Usage:

    >>> with open('big.csv') as f:
    >>>     for r in rows(f):
    >>>         process(r)
    """
    curr_row = ''
    while True:
        chunk = f.read(chunksize)
        if chunk == '': # End of file
            yield curr_row
            break
        while True:
            i = chunk.find(sep)
            if i == -1:
                break
            yield curr_row + chunk[:i]
            curr_row = ''
            chunk = chunk[i+1:]
        curr_row += chunk

Ich konnte es erfolgreich zur Lösung verschiedener Probleme einsetzen. Es wurde ausgiebig getestet, mit verschiedenen Chunk-Größen. Hier ist das Testpaket, das ich verwende, für diejenigen, die sich selbst überzeugen müssen:

test_file = 'test_file'

def cleanup(func):
    def wrapper(*args, **kwargs):
        func(*args, **kwargs)
        os.unlink(test_file)
    return wrapper

@cleanup
def test_empty(chunksize=1024):
    with open(test_file, 'w') as f:
        f.write('')
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 1

@cleanup
def test_1_char_2_rows(chunksize=1024):
    with open(test_file, 'w') as f:
        f.write('|')
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 2

@cleanup
def test_1_char(chunksize=1024):
    with open(test_file, 'w') as f:
        f.write('a')
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 1

@cleanup
def test_1025_chars_1_row(chunksize=1024):
    with open(test_file, 'w') as f:
        for i in range(1025):
            f.write('a')
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 1

@cleanup
def test_1024_chars_2_rows(chunksize=1024):
    with open(test_file, 'w') as f:
        for i in range(1023):
            f.write('a')
        f.write('|')
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 2

@cleanup
def test_1025_chars_1026_rows(chunksize=1024):
    with open(test_file, 'w') as f:
        for i in range(1025):
            f.write('|')
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 1026

@cleanup
def test_2048_chars_2_rows(chunksize=1024):
    with open(test_file, 'w') as f:
        for i in range(1022):
            f.write('a')
        f.write('|')
        f.write('a')
        # -- end of 1st chunk --
        for i in range(1024):
            f.write('a')
        # -- end of 2nd chunk
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 2

@cleanup
def test_2049_chars_2_rows(chunksize=1024):
    with open(test_file, 'w') as f:
        for i in range(1022):
            f.write('a')
        f.write('|')
        f.write('a')
        # -- end of 1st chunk --
        for i in range(1024):
            f.write('a')
        # -- end of 2nd chunk
        f.write('a')
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 2

if __name__ == '__main__':
    for chunksize in [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]:
        test_empty(chunksize)
        test_1_char_2_rows(chunksize)
        test_1_char(chunksize)
        test_1025_chars_1_row(chunksize)
        test_1024_chars_2_rows(chunksize)
        test_1025_chars_1026_rows(chunksize)
        test_2048_chars_2_rows(chunksize)
        test_2049_chars_2_rows(chunksize)

41voto

Wenn Ihr Computer, Ihr Betriebssystem und Python 64-Bit sind dann können Sie die mmap-Modul um den Inhalt der Datei im Speicher abzubilden und mit Indizes und Slices auf ihn zuzugreifen. Hier ein Beispiel aus der Dokumentation:

import mmap
with open("hello.txt", "r+") as f:
    # memory-map the file, size 0 means whole file
    map = mmap.mmap(f.fileno(), 0)
    # read content via standard file methods
    print map.readline()  # prints "Hello Python!"
    # read content via slice notation
    print map[:5]  # prints "Hello"
    # update content using slice notation;
    # note that new content must have same size
    map[6:] = " world!\n"
    # ... and read again using standard file methods
    map.seek(0)
    print map.readline()  # prints "Hello  world!"
    # close the map
    map.close()

Wenn entweder Ihr Computer, Ihr Betriebssystem oder Python 32-Bit sind dann kann mmap-ing große Dateien große Teile des Adressraums reservieren und verhungern Ihr Programm aus dem Speicher.

14voto

myroslav Punkte 3613
f = ... # file-like object, i.e. supporting read(size) function and 
        # returning empty string '' when there is nothing to read

def chunked(file, chunk_size):
    return iter(lambda: file.read(chunk_size), '')

for data in chunked(f, 65536):
    # process the data

UPDATE: Der Ansatz wird am besten erklärt in https://stackoverflow.com/a/4566523/38592

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