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.
DerSuchende
User
Beiträge: 29
Registriert: Montag 21. Februar 2011, 07:37

Kann es sein, dass seek() in den Varianten seek(relativ, 1) und seek(anstand_vom_ende, 2) nur in Python2 aber nicht in Python3 funktionieren?
Oder hat sich die Syntax geändert?
__deets__
User
Beiträge: 14544
Registriert: Mittwoch 14. Oktober 2015, 14:29

Nicht laut Dokumentation https://docs.python.org/3/library/os.html#os.lseek - und es waere auch ungewoehnlich, weil das nur eine duenne Schicht ueber dem eigentlichen Bestriebssystemsaufruf ist. Der Fehler wird eher woanders liegen, wenn du da gerade ein Programm von 2 auf 3 portierst.
DerSuchende
User
Beiträge: 29
Registriert: Montag 21. Februar 2011, 07:37

Also ich hatte es schon auf 7-Zeiler reduziert und es ging mit Py2 tadellos und Py3 brachte die Fehlermeldung
io.UnsupportedOperation: can't do nonzero end-relative seeks

Habe im Netz aber folgenden Wrapper gefunden:

Code: Alles auswählen

import io

def textio_seek(fobj, amount, whence=0):
     fobj.buffer.seek(amount, whence)
     return io.TextIOWrapper(
         fobj.buffer, encoding=fobj.encoding, errors=fobj.errors,
         newline=fobj.newlines)

# und dann weiter:

f = textio_seek(f, 7, 2)

Ich arbeite übrigens unter Linux, aber glaube nicht, dass es daran liegt.
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@DerSuchende: Du kannst bei Textdateien halt nicht unabhängig von der Kodierung und den Zeilenenden sagen was das eigentlich bedeutet 7 (ja was eigentlich) vom Ende der Datei zurück zu gehen. Darum geht das nicht mehr. Der Wrapper da kann funktionieren, muss aber halt auch nicht.

Was willst Du da eigentlich machen?
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
DerSuchende
User
Beiträge: 29
Registriert: Montag 21. Februar 2011, 07:37

Ich hatte schon so ein ungutes Gefühl, dass es mit der Codierung zusammenhängt. Ich wollte eine Textdatei einlesen, dabei inidizieren, d.h. zwei Listen anlegen, die die jeweiligen Zeilenlängen und die kumulative Zeichenmenge an der Position x erfassen, so dass ich aus der reinen Textdatei eine Art Direktzugriffsdatei machen kann, auch wenn die Zeilen unterschiedliche Länge haben. Dann kann ich eben auf Zeile x sofort zugreifen und mir das Zeichen y herausholen und ggf. auch ändern. Und das, ohne die komplette Datei im Speicher zu halten oder für jeden Zugriff alle Zeilen von Anfang an zu durchmustern.

Aber aus der Fehlermeldung entnehme ich, dass der 2. Parameter von seek() mit 1 oder 2 nur akzeptiert wird, wenn der erste Parameter 0 ist.
Notfalls muss ich es eben mit Python 2 machen. Oder ich mache eben eine kleine Nebenrechnung und ermittle so die gesuchte Position.
Das direkte Setzen des Zeigers funktioniert ja. Das mit den relativen Zeigern ist ja nur ein Schmackerl, was notfalls verzichtbar ist.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Ändern geht mit Textdateien sowieso nicht, weil wenn sich die Länge einer Zeile ändert, die komplette Datei neu geschrieben werden muß.
Und wie Du schon schriebst, hast Du doch die absolute Position jeder Zeile.
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

@DerSuchende: Du kannst (je nach Encoding) noch nichtmal ein einzelnes Zeichen austauschen: Was soll zum Beispiel passieren, wenn du ein `a` gegen ein `🙃` tauschst?
DerSuchende
User
Beiträge: 29
Registriert: Montag 21. Februar 2011, 07:37

Stimmt, ihr habt recht. Habe mir die Datei nochmal im Hexeditor angeguckt. Klar, woher soll das arme Script wissen, wieviele Byte meine unterschiedlichsten Zeichen haben. Da ich meine Anwendung glücklicherweise nicht für Büroarbeiten einsetzen will, sondern für technische Steuerungen obliegt es mir, nur 1-Byte-Zeichen anzuwenden und dann geht es ja. Und dann ändert sich beim editieren einzelner Zeichen auch nicht die Dateilänge. Ich hatte für kurze Zeit zu sorglos drauflos programmiert bis ich stolperte. Unser schönes Python hat nämlich keine wirklichen Grenzen, man muss eben nur mitdenken :-)
Aber vielleicht sind feste Zeilenlängen dabei auch noch der einfachere Weg.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Vielleicht ist ein anderes Datenformat auch das richtige.
Was willst Du eigentlich mit Deinen Daten machen?
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Und: Sind die Dateien wirklich so riesig, dass man sie nicht im Speicher halten kann?
DerSuchende
User
Beiträge: 29
Registriert: Montag 21. Februar 2011, 07:37

also es handelt sich um eine Art Log-Dateien, wobei jede Zeile für einen Tag steht. Die Anzahl der Einträge können von Tag zu Tag variieren. Ich wollte lesend nur gezielt auf ein Datum springen können, ohne alle Zeilen davor durchlaufen zu müssen. Die Menge der Daten ist noch unklar. Aber es passt natürlich einiges in den RAM hinein. Ihr denkt wohl mehr an ein Directory mit Datum als Schlüssel und das ganze gepickelt?
Das Ändern von Dateneinträgen ist nicht wichtig, es kommt auf den schnellen Zugriff an. Aber die Daten entstehen eben Log-Datei-typisch seriell. Deshalb war ich für die Rohdaten von einer Textdatei ausgegangen. Aber ich werde nochmal in Ruhe drüber nachdenken. Efffektive Datenstrukturen ersparen später viel Programmierarbeit.
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Ich glaube, du machst dir zu viele Gedanken um Probleme, die noch gar nicht aufgetreten sind.
Und inzwischen bist du sehr weit vom Start des Themas entfernt. Denn dort wolltest du ja noch einzelne Bytes ersetzen - jetzt müssen die Daten gar nicht geändert werden?!

Wenn du wirklich wahlfreien Zugriff auf einzelne Datensätze brauchst - und das möglichst fix - macht auch pickle keinen Sinn.
Je nach Art, was dort gelockt wird, würde ich da mal sqlite Vorschlagen.
Benutzeravatar
snafu
User
Beiträge: 6742
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Du könntest dir auch die Dateilänge merken. Wenn diese sich durch einen neuen Log-Eintrag verändert, liest du die neuen Daten ab der alten Länge.

Wobei Python sehr effizient ist beim Verarbeiten von Text-Streams. Bist du sicher, dass es soviel ausmacht, wenn die Zeilen wiederholt komplett durchlaufen werden? seek() ist halt etwas tricky, weil man sich darauf verlassen muss, dass es auf jedem System korrekt bzw wie erwartet funktioniert.
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

DerSuchende hat geschrieben: Sonntag 4. Juli 2021, 20:19 also es handelt sich um eine Art Log-Dateien, wobei jede Zeile für einen Tag steht. Die Anzahl der Einträge können von Tag zu Tag variieren. Ich wollte lesend nur gezielt auf ein Datum springen können, ohne alle Zeilen davor durchlaufen zu müssen. Die Menge der Daten ist noch unklar. Aber es passt natürlich einiges in den RAM hinein. Ihr denkt wohl mehr an ein Directory mit Datum als Schlüssel und das ganze gepickelt?
Das Ändern von Dateneinträgen ist nicht wichtig, es kommt auf den schnellen Zugriff an. Aber die Daten entstehen eben Log-Datei-typisch seriell. Deshalb war ich für die Rohdaten von einer Textdatei ausgegangen. Aber ich werde nochmal in Ruhe drüber nachdenken. Efffektive Datenstrukturen ersparen später viel Programmierarbeit.
Man könnte die Daten in eine Art von Datenbank stecken. Speziell für Logdaten gibt es da tolle Lösungen, von der PostgreSQL-Erweiterung Timescaledb über eine Suchmaschine mit Zeitreihenfunktion wie Elasticsearch, bis hin zu einer In-Memory-Lösung wie Redis. Okay, Elasticsearch ist für diesen Anwendungsfall vielleicht etwas überdimensioniert... ;-) All diese Dinge sind sehr einfach aufzusetzen und zu betreiben, zumal Redis und PostgreSQL in so ziemlich allen, und Timescaledb in vielen Distributionen bereits in den Repositories der Distributoren enthalten sind.

Nebenbei könntest Du Deine Datei auch einfach mit mmap in den Speicher mappen, aber ehrlich gesagt... Ein Eintrag pro Tag, das wären popelige 3600 Einträge in zehn Jahren, ich bitte Dich -- das ist doch gar nichts, wenn da nicht jeder Eintrag extrem viele Datenpunkte hat. Insofern befürchte ich, daß Deine Überlegungen hinsichtlich Performance und "nicht alle Daten in den Speicher laden" erstens ein Fall von "premature optimization" sind, und mit großer Wahrscheinlichkeit ohnehin fehlgehen -- denn moderne Betriebssysteme wie Linux haben da so etwas, das nennt sich Dateisystempuffer, das findest Du in der Ausgabe von top(1) und free(1) unter "buff/cache" (auf deutsch: "Puff/Cache" in top(1) und "Zwischen" in free(1)). Da wird die Datei beim Öffnen ohnehin in den Arbeitsspeicher, mithin: den Dateisystempuffer geladen, völlig unabhängig davon, ob Du die Datei dann einfach seriell liest oder darin mit seek darin herumspringst. Von daher bitte Donald E. Knuth nicht vergessen: "premature optimization is the root of all evil". ;-)
DerSuchende
User
Beiträge: 29
Registriert: Montag 21. Februar 2011, 07:37

Erst einmal vielen Dank für die vielen wertvollen Überlegungen. Wenn ich auf die ursprüngliche Idee verzichte, in der Datei byteweise herumschreiben zu wollen, funktioniert es jetzt doch, da ich mich lesend immer an den Wagenrückläufen entlanghangele. Hier mal mein Versuch, um meine eigentliche Absicht zu demonstrieren:

Code: Alles auswählen

# Seriell2Direktzugriff

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

posindex = [0,]
zuletzt = 0

# Index für Testfile aufbauen
f = open('text', 'r')
while True:
    zeile = f.readline()
    if zeile == '': break
    zuletzt += len(zeile)
    posindex.append(zuletzt)
    # print(zeile[:-1])
f.close()

# Zeigen der Indexliste
print(posindex)

# Heraussuchen einer vorgebenen Zeile
suchpos = 5    
f = open('text', 'r')
f.seek(posindex[suchpos])
ergebnis = f.read(posindex[suchpos+1] - posindex[suchpos])
print(f.tell())
print('['+ergebnis[:-1]+']')
f.close()
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. Es war eigentlich nur ein neugieriger Versuch, ob das so auch gehen würde.
Also nochmals vielen Dank, die Denkanstöße haben mir trotzdem geholfen, manche schon
im Vorgriff auf kommende Projekte.
Sirius3
User
Beiträge: 17754
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: 17754
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.
Antworten