Seite 1 von 2

seek() in Python3

Verfasst: Sonntag 4. Juli 2021, 14:40
von DerSuchende
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?

Re: seek() in Python3

Verfasst: Sonntag 4. Juli 2021, 14:45
von __deets__
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.

Re: seek() in Python3

Verfasst: Sonntag 4. Juli 2021, 15:04
von DerSuchende
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.

Re: seek() in Python3

Verfasst: Sonntag 4. Juli 2021, 15:26
von __blackjack__
@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?

Re: seek() in Python3

Verfasst: Sonntag 4. Juli 2021, 15:47
von DerSuchende
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.

Re: seek() in Python3

Verfasst: Sonntag 4. Juli 2021, 16:58
von Sirius3
Ä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.

Re: seek() in Python3

Verfasst: Sonntag 4. Juli 2021, 17:40
von narpfel
@DerSuchende: Du kannst (je nach Encoding) noch nichtmal ein einzelnes Zeichen austauschen: Was soll zum Beispiel passieren, wenn du ein `a` gegen ein `🙃` tauschst?

Re: seek() in Python3

Verfasst: Sonntag 4. Juli 2021, 17:46
von DerSuchende
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.

Re: seek() in Python3

Verfasst: Sonntag 4. Juli 2021, 17:57
von Sirius3
Vielleicht ist ein anderes Datenformat auch das richtige.
Was willst Du eigentlich mit Deinen Daten machen?

Re: seek() in Python3

Verfasst: Sonntag 4. Juli 2021, 19:23
von sparrow
Und: Sind die Dateien wirklich so riesig, dass man sie nicht im Speicher halten kann?

Re: seek() in Python3

Verfasst: Sonntag 4. Juli 2021, 20:19
von DerSuchende
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.

Re: seek() in Python3

Verfasst: Sonntag 4. Juli 2021, 20:37
von sparrow
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.

Re: seek() in Python3

Verfasst: Sonntag 4. Juli 2021, 20:40
von snafu
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.

Re: seek() in Python3

Verfasst: Sonntag 4. Juli 2021, 21:05
von LukeNukem
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". ;-)

Re: seek() in Python3

Verfasst: Montag 5. Juli 2021, 04:58
von DerSuchende
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.

Re: seek() in Python3

Verfasst: Montag 5. Juli 2021, 07:26
von Sirius3
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]}]')

Re: seek() in Python3

Verfasst: Montag 5. Juli 2021, 09:09
von DerSuchende
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.

Re: seek() in Python3

Verfasst: Montag 5. Juli 2021, 09:19
von DerSuchende
Nun habe ich beim Herumprobieren endlich noch bemerkt. welche vielen kleinen Verbesserungen neu hinzugekommen sind. Danke dafür.

Re: seek() in Python3

Verfasst: Montag 5. Juli 2021, 09:19
von Sirius3
Dein Kniff funktioniert halt nicht, und Dein Test deckt nicht alle Fälle ab.

Re: seek() in Python3

Verfasst: Montag 5. Juli 2021, 09:33
von DerSuchende
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.