Seite 1 von 1

Blockweises Lesen einer Datei in Python...

Verfasst: Montag 6. Februar 2012, 12:06
von akis.kapo
Hi Jungs (und Mädels),

ich erwische mich immer wieder dabei, wie ich in meinem Python Code
schlechten (nicht-python) Stil verwende, zum Beispiel, wenn es um
das blockweise Lesen von Dateien geht.

Hier eines, meiner vielen Negativbeispiele:

Code: Alles auswählen

f = open('hallo.txt','r')
blocksize = 4096
chunk = f.read(blocksize)
while len(chunk) > 0:
    do_something(chunk)
    chunk = f.read(blocksize)
f.close()
Wie löst man das in Python? (2.7.x und 3.x)

Python 3.x hat ja das IO komplett ungekrempelt.
Wie baut man das im obigen Code richtig ein?

Was mich stört:
1. blocksize will ich nicht selber wählen, sondern ausm System ziehen
2. chunk ist oben ein Objekt, das immer wieder erzeugt und zerstört wird.
(lieber _ein_ StringIO(2.7.x)/io.BytesIO(3.x) Objekt nehmen? Keine Ahnung...)
3. natürlich der while loop mit der hässlichen condition und der
Tatsache, dass ich chuck "initialisieren" muss, um in die while Schleife zu kommen.
Das kommt mir zu old-school C-Style vor. Das ist kein "schönes" Python. :-/

Bin um jeden konstruktiven Beitrag froh.

Vielen Dank im Voraus.

PS: sagt bloß nicht, ihr würdet es alle auch so schreiben, des kann's doch net sein... :shock:

Re: Blockweises Lesen einer Datei in Python...

Verfasst: Montag 6. Februar 2012, 12:13
von Hyperion
Dateien sollte man mit dem ``with open as handler``-Idiom öffnen:

Code: Alles auswählen

with open(...) as handler:
    # handler ist hier File-Objekt
    # handler wird automatisch geschlossen
Das wäre schon mal die erste sinnvolle Verbesserung.

Als nächstes: Was stellst Du Dir denn unter einer "System weiten Blockgröße" vor? Meinst Du die block size des Dateisystems? Wenn ja, was soll das bringen?

Wenn Du Textdateien nicht komplett in den Speicher laden willst, so kannst Du bei diesen auf direkt über die Zeilen iterieren - das spart ja auch massiv Speicher und bietet sich bei Textdateien ja auch inhaltlich geradezu an. Aber vielleicht hast Du da ja auch eine andere Motivation?

Re: Blockweises Lesen einer Datei in Python...

Verfasst: Montag 6. Februar 2012, 12:20
von lunar
@akis.kapo: Wenn Daten blockweise gelesen werden müssen, handelt es sich meist um Binärdaten, und mithin muss der Modus zum Öffnen "rb" lauten. Um die "while"-Schleife zu vermeiden, kann man eine partielle Funktion ("functools.partial()") in Verbindung mit der eingebauten Funktion "iter()" verwenden:

Code: Alles auswählen

with open(filename, 'rb') as source:
    for block in iter(partial(source.read, blocksize)):
        process(block)
Was meinst Du mit "blocksize aus dem System ziehen"? Falls Du darauf hinaus willst, irgendeine Systemeinstellung zur Standard-Blockgröße zu verwenden: Eine solche Einstellung gibt es nicht. Die richtige Blockgröße hängt von den verarbeiteten Daten, der Umgebung und vielem mehr ab, die richtige Größe gibt es mithin nicht. Du musst selbst die passende Blockgröße wählen, oder - im Falle einer Bibliothek - eventuell dem Aufrufer Deiner Funktion mithilfe eines Parameters die Wahl der richtigen Blockgröße ermöglichen.

Die wiederholte Initialisierung und Zerstörung temporärer Objekte wie beispielsweise "chunk" lässt sich nicht so ohne Weiteres verhindern, das ist einfach Bestandteil der Semantik von Python. Wieso stört Dich das? Wenn Du über Allokation und Initialisierung von Objekten nachdenkst, dann ist Python möglicherweise nicht die richtige Sprache für Dich. Anders gesagt, über sowas denkt man in C oder C++ nach, aber nicht in Python (oder anderen Hochsprachen wie C# oder Java).

Re: Blockweises Lesen einer Datei in Python...

Verfasst: Montag 6. Februar 2012, 13:41
von akis.kapo
@lunar

Wow, super. Ich weiss selber, dass ich zu sehr "C" denke, daher frag ich ja,
wie man es nicht nur in Python schreibt, sondern auch in Python denkt!

Code: Alles auswählen

import io
from functools import partial

with open(filename, 'rb') as source:
    for block in iter(partial(source.read, io.DEFAULT_BUFFER_SIZE)):
        do_something(block)
Genauso in der Art hätte ich es mir vorgestellt.

@Hyperion
Das "with" construct war mir vorher schon bekannt, darin lag mein Fokus jetzt nicht, aber trotzdem ein guter Reminder! Danke.

@all

ich weiss ja nicht im Detail, wie es in CPython implementiert ist, aber ich kann mir durchaus vorstellen,
dass Schleifenobjekte "for <var> in ..." anders gehandhabt werden, als manuelle Konstrukte a la:

Code: Alles auswählen

my = "hello"
while len(hello) > 0:
    do_something(my)
    my = my * 2
Ich wette, das wirkt sich auf die Garbage Collection & Performance "anders" aus.
Kann aber auch nur ne falsche Annahme sein, ... vielleicht bin ich auch paranoid. :)

Re: Blockweises Lesen einer Datei in Python...

Verfasst: Montag 6. Februar 2012, 14:38
von lunar
@akis.kapo: Ich kenne mich mit der Implementierung von CPython nicht aus, doch ich kann mir gut vorstellen, dass CPython einen internen Speicherpool verwaltet, und für solche temporären Objekte nicht immer erneut Speicher allozieren muss, sondern alte, nicht mehr referenzierte Objekte wiederverwendet. Ähnliches gilt sicherlich auch für alternative Interpreter wie PyPy, die eine fortschrittlichere Garbage Collection besitzen.

Re: Blockweises Lesen einer Datei in Python...

Verfasst: Montag 6. Februar 2012, 15:02
von akis.kapo
whoopsy, zu früh gefreut:

Code: Alles auswählen

TypeError: 'functools.partial' object is not iterable

Re: Blockweises Lesen einer Datei in Python...

Verfasst: Montag 6. Februar 2012, 15:08
von cofi
Ad ``io.DEFAULT_BUFFER_SIZE``: Das hier ist die Implementierung:

Code: Alles auswählen

# open() uses st_blksize whenever we can
DEFAULT_BUFFER_SIZE = 8 * 1024  # bytes
Du brauchst dafuer also einen manuellen Aufruf von ``os.stat``, noch dazu ist die blksize nur auf Unix verfuegbar. die 8 KiB sind fuer die meisten Systeme afaik falsch, der gaengige Block ist 4KiB.

Ad ``iter`` Fehler, du hast den Sentinal vergessen, den man bei einem Callable braucht, versuch mal:

Code: Alles auswählen

iter(partial(source.read, io.DEFAULT_BUFFER_SIZE), '')

Re: Blockweises Lesen einer Datei in Python...

Verfasst: Montag 6. Februar 2012, 15:31
von akis.kapo
@cofi

many thanks! gerade so noch die Kurve gekriegt... darauf wäre ich alleine nicht gekommen. Hab schon angefangen, ne pseudoklasse drumherum zu basteln, nur damit das Ding iter() kann. (Und es sah unendlich hässlich aus...)

PS: habe soeben in der libref nachgelesen, wozu dieser Sentinel gut sein soll. Wenn der Rückgabewert == dem Sentinel ist, weiss iter(), wann es aufzuhören hat. Schade eigentlich, ... ich hatte da an X-Men gedacht. . :P

Re: Blockweises Lesen einer Datei in Python...

Verfasst: Montag 6. Februar 2012, 15:49
von snafu
Du kannst dir folgendermaßen die Schleife sparen (ungetestet):

Code: Alles auswählen

''.join(iter(partial(source.read, io.DEFAULT_BUFFER_SIZE), ''))
Bedenke aber, dass dann auch der komplette Dateiinhalt im Speicher ist und die weitere Bearbeitung natürlich erst stattfindet, wenn die Datei / der Stream komplett eingelesen wurde. Wirklich viel Peformance bringt das aber nicht und die sollte hier ohnehin zweitrangig bleiben. So gesehen muss ja allein schon `read()` immer wieder neu aufgerufen werden (Funktionsaufrufe sind "teuer"). Das sind aber alles Gedanken, die in die falsche richtig gehen, d.h. Optimierungen, wo eigentlich keine Optimierungen nötig sind.

Re: Blockweises Lesen einer Datei in Python...

Verfasst: Montag 6. Februar 2012, 16:01
von akis.kapo
@snafu
Ja nee, das ist ja auch explizit nicht gewollt, eben weil ich nicht so viel Speicher auf einen Schlag habe und weil do_something() mit kleinen Häppchen besser arbeitet...

Re: Blockweises Lesen einer Datei in Python...

Verfasst: Montag 6. Februar 2012, 16:39
von lunar
@akis.kapo: Oh, Entschuldigung, ich hatte beim Aufruf das zweite Argument vergessen. Tut mir leid.

@cofi: Danke für die Korrektur.