seek() in Python3

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Sirius3
User
Beiträge: 18265
Registriert: Sonntag 21. Oktober 2012, 17:20

Dateien öffnet man immer mit Hilfe des with-Statements.
Statt der while-Schleife benutzt man eine for-Schleife.
Und Du hast jetzt genau den Fehler gemacht, vor dem wir hier die ganze Zeit warnen. Zeichen können aus mehreren Bytes bestehen, so dass die Länge eines Strings nicht der Anzahl der Bytes in der Datei entspricht.
Du mußt im Binären Modus arbeiten.

Code: Alles auswählen

# Testfile erzeugen
with open('text', 'w') as file:
    file.write('Hallo\n')
    file.write('Du wohnst\n')
    file.write('ÄÖÜäöüß[]{}€#><*\n')
    file.write('zu Hause\n')
    file.write('Und Du bist\n')
    file.write('hier der Chef!\n')


# Index für Testfile aufbauen
with open('text', 'rb') as file:
    posindex = [0]
    for zeile in file:
        posindex.append(file.tell())

# Zeigen der Indexliste
print(posindex)

# Heraussuchen einer vorgebenen Zeile
suchpos = 2    
with open('text', 'rb') as file:
    file.seek(posindex[suchpos])
    ergebnis = file.read(posindex[suchpos+1] - posindex[suchpos]).decode()
    print(file.tell())
    print(f'[{ergebnis[:-1]}]')
DerSuchende
User
Beiträge: 29
Registriert: Montag 21. Februar 2011, 07:37

Das mit dem with-Statement ist schon klar, war 'ne alte Gewohnheit mit open. Du hast meinen Kniff nicht vollständig wahrgenommen. Wie Du siehst, ist in der Datei z.B. auch das mehrbytige €-Zeichen und es funktioniert doch, weil ich nämlich genau die einzelnen Bytes garnicht adressiere sondern immer nur in die '\n' einraste. Dadurch wird nach read() aus der Zeile ein String, in dem ich wieder klassisch lesend herummarschieren kann. Das habe ich alles deshalb so gemacht, weil ich natürlich genau Deinen Hinweis beachtet habe und die angedrohten Auswirkungen vermieden habe. Ich habe natürlich das "falsche Vorgehen" ausprobiert, um zu sehen, was passiert und genau, wie Du es vorausgesagt hast, hat es genau dann geknallt, wenn ich eines der Mehrfachbytes anzusteuern versuchte. Keine Sorge, ich bin kein Ignorant, der gutgemeinte Hinweise einfach in den Wind schlägt.:-)
Habe es übrigens ausprobiert, ob es auch mit Unicode-Zeichen geht, z.B. '√ ' und es wird geliefert, natürlich nur untergebracht im String, wie oben beschrieben. Mir ist jetzt auch an Deinem Beispiel richtig bewusst geworden, dass das with ohne close() auskommt.
DerSuchende
User
Beiträge: 29
Registriert: Montag 21. Februar 2011, 07:37

Nun habe ich beim Herumprobieren endlich noch bemerkt. welche vielen kleinen Verbesserungen neu hinzugekommen sind. Danke dafür.
Sirius3
User
Beiträge: 18265
Registriert: Sonntag 21. Oktober 2012, 17:20

Dein Kniff funktioniert halt nicht, und Dein Test deckt nicht alle Fälle ab.
DerSuchende
User
Beiträge: 29
Registriert: Montag 21. Februar 2011, 07:37

Als ich Deine vielen kleinen Verbesserungen entdeckt habe, habe ich zuerst gesehen, dass es so viel eleganter ist. Aber inzwischen wundere ich mich selber, dass es bei mir den Anschein hatte, dass meine Version funktionieren würde.
Benutzeravatar
__blackjack__
User
Beiträge: 14030
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ich würde behaupten auch die Version von Sirius3 ist noch nicht korrekt weil ``for line in file:`` nicht zeilenweise lesen muss und das AFAIK auch tatsächlich nicht tut. Das *liefert* zeilenweise, liest aber in grösseren Blöcken und puffert, aus Effizienzgründen. Wenn man tatsächlich garantiert Zeile für Zeile lesen will, muss man explizit `readline()` für jede Zeile benutzen. Zum Beispiel als ``for line in iter(file.readline, b""):``.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Sirius3
User
Beiträge: 18265
Registriert: Sonntag 21. Oktober 2012, 17:20

@__blackjack__: ob irgendwo intern gepuffert wird, ist doch für den Anwender egal. `open` liefert immer einen BufferedReader, `readline` verhält sich also genau gleich wie der Iterator.
Benutzeravatar
__blackjack__
User
Beiträge: 14030
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Sirius3: Dann ist das neu in Python 3. In Python 2 konnte man iterieren und `readline()` nicht mischen weil iterieren gepuffert hat und `readline()` die C-Funktion auf der Datei direkt verwendet hat.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

So ganz egal ist internes puffern nicht, den dass führt ja dazu dass es Unterschied gibt zwischen deiner Position aus Perspektive des Betriebssystems und deiner Anwendung. Das muss ja irgendwie in Einklang gebracht werden.

Wenn man die Datei übrigens in "text mode" öffnet bekommt man etwas zurück dass TextIOBase implementiert. .tell() und .seek() auf TextIOBase arbeiten nicht in bytes: https://docs.python.org/3/library/io.ht ... OBase.seek. Ich denke mal damit wird vermieden dass man invalide Strings durch ein seek an die falsche Stelle produziert.
Sirius3
User
Beiträge: 18265
Registriert: Sonntag 21. Oktober 2012, 17:20

@DasIch: es ist in soweit egal, weil ich als Anwender die Dateipositionen des Betriebssystems gar nicht zu Gesicht bekomme.
Und doch, tell und seek arbeiten auch bei Textdateien auf Byteniveau, weshalb es ja zu Problemen kommen kann, wenn man das mischt.
offset must either be a number returned by TextIOBase.tell(), or zero. Any other offset value produces undefined behaviour.
Benutzeravatar
snafu
User
Beiträge: 6862
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Also tell() sollte schon das liefern, was auf "seiner" Abstraktionsebene als aktuelle Position betrachtet wird. Irgendwelche internen Puffer-Positionen hätten da wenig Sinn für den Anwender. 🤷‍♂️

Und was ein Zeichen ist, hängt ja von der Konfiguration des Wrappers ab. Dementsprechend sind das auf unterster Ebene einzelne Bytes, können auf höheren Ebenen aber durchaus Codepoints, d.h. auch mehrere Bytes sein. Wenn man beim zum tell() passenden seek() bleibt, dann spielt das aber ohnehin keine Rolle.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Sirius3 hat geschrieben: Montag 5. Juli 2021, 14:39 Und doch, tell und seek arbeiten auch bei Textdateien auf Byteniveau, weshalb es ja zu Problemen kommen kann, wenn man das mischt.
offset must either be a number returned by TextIOBase.tell(), or zero. Any other offset value produces undefined behaviour.
Das sagt ja nur dass eine Nummer erwartet die von .tell() kommt, nicht dass die Number sich auf bytes bezieht. Liest man ein bisschen weiter um zu sehen was .tell() dazu sagt trifft man dann auf:
Return the current stream position as an opaque number. The number does not usually represent a number of bytes in the underlying binary storage.
Benutzeravatar
snafu
User
Beiträge: 6862
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Nachdem ich nun den Code für seek() und tell() jeweils für den Text- und Binärmodus gesehen habe, würde ich das nur im Binärmodus empfehlen, wie ja schon gezeigt wurde. Im Text-Modus ist das alles sehr kompliziert programmiert, kann in irgendwelchen "Illegal States" landen und wirkt insgesamt jetzt nicht sooo vertrauenserweckend auf mich (nichts für ungut), wobei ich da eh irgendwann aufgegeben habe. f.tell() während man zeilenweise iteriert, wirft sowieso einen OSError im Textmodus, während es im Binärmodus ("rb") klappt wie erwartet. Die Positionsangaben entsprechen im Binärmodus tatsächlich den Bytes, da auf einem Char-Pointer herumgesprungen wird. Keine Ahnung, warum die Doku da so vage ist. Vielleicht wollte man es als Implementierungsdetail belassen und daher nicht näher festlegen.

EDIT: Das soll jetzt nicht heißen, dass tell() im Textmodus aus meiner Sicht grundsätzlich kaputt wäre. Aber in Abhängigkeit von Encoding und Verwendung wären mir das zu viele Unsicherheiten. Soll aber natürlich jeder selbst entscheiden.
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

DerSuchende hat geschrieben: Montag 5. Juli 2021, 04:58 Wenn auch meine Dateien komplett in den Arbeitsspeicher passen würden, so wollte ich
nicht mit einer Schaufel Erde nach einer Fliege werfen, nur weil genügend Erde vorrätig
ist.
Premature optimization. Du denkst über Dinge nach, die Dein Betriebssystem viel besser kann als Du. Und schießt Dir dabei, Pardon: in Deine eigenen Füße. ;-)
DerSuchende
User
Beiträge: 29
Registriert: Montag 21. Februar 2011, 07:37

Premature optimization. Du denkst über Dinge nach, die Dein Betriebssystem viel besser kann als Du. Und schießt Dir dabei, Pardon: in Deine eigenen Füße. ;-)
Nun, da die hier erarbeitete Lösung ja im Prinzip offensichtlich funktioniert, ist der Schuss wohl an den Füßen vorbeigegangen. Es ging ja nur darum, einen etwas intelligenteren Zugriff auszuprobieren. Und wenn es zuletzt nur ein Gedankenspiel bleibt, so ist doch Python dafür genau die richtige Spielwiese :-)
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Naja, so gut sind Betriebssysteme beim Swapping auch nicht, erst recht nicht wenn man das Zugriffspattern nicht kommuniziert. Letztendlich passen Daten aber in der Praxis nahezu immer in den Arbeitsspeicher, die Frage ist nur ob du dir es auch leisten kannst.
Benutzeravatar
snafu
User
Beiträge: 6862
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@DerSuchende: Ist jetzt eigentlich diese Lösung dein Favorit? Dir ist schon klar, dass hier immer noch alle Zeilen durchlaufen werden, um die "Breakpoints" zu ermitteln? Die ermittelte Struktur samt Zugriff ist ja nur hilfreich bei langlebigen Programmen und nicht, wenn der gesamte Ablauf immer wieder neu angestoßen wird. Welches von beiden wäre denn dein Szenario? Ist vielleicht nth() bzw nth_or_last() aus dem (externen) more_itertools-Modul schon ausreichend für deine Zwecke? Das Iterable wäre dabei halt dein Stream, der die einzelnen Zeilen liefert. Dann benötigt man kein tell() und seek() für die Wahl einer einzelnen Zeile.
DerSuchende
User
Beiträge: 29
Registriert: Montag 21. Februar 2011, 07:37

@DasIch: Beim Arbeitsspeicherverbrauch wollte ich sparsam vorgehen, damit eine Lösungsidee leicht angepasst eventuell auch unter MikroPython einsetzbar bleibt. Allerdings bin ich mir nicht sicher, ob das Python nicht trotzdem hintereinander n Datensätze hereinholt, wenn ich den Datensatz n+1 adressiere. Das muss ich nochmal hinterleuchten. Unter dem Blickwinkel ist es vielleicht doch besser, wenn ich, wie üblich, die Zeilen nacheinander überschreibend einlese, bis Zeile n endlich kommt.
DerSuchende
User
Beiträge: 29
Registriert: Montag 21. Februar 2011, 07:37

@snafu: Also so richtig glücklich bin ich mit dieser Vorgehensweise nicht, denn ich bin mir nicht sicher, ob alle Schwierigkeiten beseitigt sind.
Aber da durch das Aufzeichnen der Positionen des Lesezeigers immer auf das Ende einer Zeile gezeigt wird, was ja das einbytige '\n' ist, wird niemals danebengegriffen. Und die dann treffsicher herausgelesene Zeile ist ja dann ja ein String (weil wir ja von einer Textdatei reden) und aus dem kann ich ja durch string[index] jede beliebige Position sauber ansteuern. Aber wenn ich mir überlege wie einfach es ist, mit readlines alles in einer einzigen Liste vorzufinden, mit der man bequem arbeiten kann, überlege ich es mir noch 3x, ob ich mir die Ursprungsidee wirklich antun muss. Aber es war eben so ein verrückter Gedanke aus meiner Vor-Python-Zeit, wo man selbstgeschrienbene Routinen auf Direktzugriffsdateien losgelassen hat, die im dBase-Format organisiert waren. :-)
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

DerSuchende hat geschrieben: Montag 5. Juli 2021, 04:58 Wenn auch meine Dateien komplett in den Arbeitsspeicher passen würden, so wollte ich
nicht mit einer Schaufel Erde nach einer Fliege werfen, nur weil genügend Erde vorrätig
ist.
Stattdessen einen Bagger zu bauen, um dann mit dessen Schaufel nach der Fiege zu schlagen, ist jedenfalls... kreativ. ;-)
Antworten