Kontinuierliches Einlesen von mehreren Zeilen aus *.txt

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.
Antworten
Mauli
User
Beiträge: 10
Registriert: Donnerstag 5. April 2007, 09:03

Donnerstag 5. April 2007, 09:49

Hallo erstmal,
trotz intensiver Recherche in diesem Forum zum Thema Einlesen aus txt-files schaffe ich es leider nicht mein Problem zu lösen.

Ich habe eine txt-Datei die aus zwei Zahlenspalten besteht, wovon genau eine eingelesen wird. Dabei möchte ich jedoch nicht die ganze Datei in den Speicher laden, sondern immer nur 5 Zeilen lesen, mit den Werten dieser 5 Zeilen weiterarbeiten und schließlich die nächsten 5 Zeilen lesen. Und das Ganze über ca. 20.000 Zeilen.

Meine derzeitige Funktion liest die ersten 5 Zeilen ( = Zeile 1 bis 5) richtig ein. Anschließend werden aber nicht die nächsten 5 Zeilen eingelesen (d.h. Zeile 5-10) sondern Zeile (2-6) beim nächsten Durchlauf der for-Schleife dann (3-7), usw... Da habe ich dann alles doppelt und dreifach :(

Mein Code:

Code: Alles auswählen

def readfile(filename):
    file = open(filename, "r")
    while True:
        for i in range(5):
            line = file.readline()
            if not line:
                break
            s = str.split(line)
            print y         # zur Kontrolle
            y[i] = float(s[1])
    ## ...mach was mit den 5 Werten...##
    file.close()
    return


Offensichtlich verstehe ich das Iterationsverhalten von file bzw. line nicht wirklich. Kann mir bitte jemand einen Rat geben? Und geht das nur mit seek() und tell() ? Eigentlich würde ich gerne darauf verzichten.

Gruß,
Mauli
Benutzeravatar
jens
Moderator
Beiträge: 8481
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Donnerstag 5. April 2007, 10:20

irgendwie ist das IMHO auch durcheinander...

vergleiche mal hiermit:

Code: Alles auswählen

def readfile(filename):
    f = open(filename, "r")
    while True:
        for i in range(5):
            line = f.readline()
            if not line:
                break
                
            s = line.split()
            y[i] = float(s[1])
            
        print y         # zur Kontrolle
        ## ...mach was mit den 5 Werten...##
        
    f.close()
    return
Beachte das andere Einrücken bei Zeile 13...

Poste mal Beispieldaten...

btw. du könntest auch .readlines(5) machen...

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Zap
User
Beiträge: 533
Registriert: Freitag 13. Oktober 2006, 10:56

Donnerstag 5. April 2007, 10:48

jens hat geschrieben: btw. du könntest auch .readlines(5) machen...
Ähm irgendwie kommt das nicht hin...

Code: Alles auswählen

>>>
>>> f = open("Test.txt","w")
>>> f.writelines([ "Zeile:%i\n" % i for i in range (1,10)])
>>> f.close()
>>> f = open("Test.txt","r")
>>> f.readlines(5)
['Zeile:1\n', 'Zeile:2\n', 'Zeile:3\n', 'Zeile:4\n', 'Zeile:5\n', 'Zeile:6\n', '
Zeile:7\n', 'Zeile:8\n', 'Zeile:9\n']
>>>
Was mich zudem noch interessieren würde, wie kann ein file-Object wieder auf start setzen wenn ich einmal durch iteriert habe?!

Muss ich das dann schliessen?

@ Mauli: Willkommen im Forum ;)

Nur so als Tipp, der jens hat das bei dir auch schon berichtigt
man sollte besser nicht

Code: Alles auswählen

file = open(filename, "r")
schreiben, da file das selbe ist wie open und du es damit üebrschreibst.
Das ist erstmal nicht weiter tragisch aber es ist besser einen nicht "reservierten" Namen zu verwenden.

Was ich nicht ganz an deiner Funktion verstehe du schmeisst da ein
print y raus aber im namespace der Funktion hast du y nie definiert?!
Soll das was globales sein?
Benutzeravatar
jens
Moderator
Beiträge: 8481
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Donnerstag 5. April 2007, 10:54

Sorry, war schwachfug mit dem f.readlines(5)...

Zum Anfang der Datei kannst du mit seek(0) springen :)

bzw. Warum nicht xreadlines() nehmen?

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Mauli
User
Beiträge: 10
Registriert: Donnerstag 5. April 2007, 09:03

Donnerstag 5. April 2007, 10:55

Zeile 13 hatte ich so eingerückt wie Du, bloß hier falsch gepostet, sry. Allerdings hatte ich das print y statement in der for-Schleife. Wenn man es in die while-Schleife packt, kommt mein gewünschtes Ergebnis. Das Einlesen klappt also zu meinem Erstaunen soweit.

readlines(5) geht, soweit mir bekannt nicht, weil der als Objekt nicht die Anzahl der Linien nimmt sondern als readlines([size]): " The optional size argument, if given, is an approximate bound on the total number of bytes in the lines returned. "

Das Problem hat sich irgendwie erledigt, danke.

Gruß,
Mauli

Edit:
Ah unsere Antworten haben sich überschnitten. Was macht xreadlines() denn? Aus dem was dazu in der Python Doku steht werde ich nicht schlau...
Zap
User
Beiträge: 533
Registriert: Freitag 13. Oktober 2006, 10:56

Donnerstag 5. April 2007, 11:02

Also .xreadlines() gibt wieder ein object zurück vom Typ file.
Aus der Doku entnehme ich nur das zum Iterieren optimiert ist

Code: Alles auswählen

>>> f = open("Test.txt","r").xreadlines()
>>> type(f)
<type 'file'>
>>>
Kannst es ja mal testen und berichten ;)
BlackJack

Donnerstag 5. April 2007, 11:25

`xreadlines()` macht im Grunde gar nichts, die Datei die Du zurückbekommst ist das Dateiobjekt selbst, dass ja "iterable" ist und genau das gleiche bietet was auch `xreadlines()` früher gemacht hat. Die Methode ist nur aus Rückwärtskompatibilität noch vorhanden, für neuen Code ist sie überflüssig.

Code: Alles auswählen

In [1]: f = open('test.txt', 'r')

In [2]: x = f.xreadlines()

In [3]: x
Out[3]: <open file 'test.txt', mode 'r' at 0xb7b464a0>

In [4]: x is f
Out[4]: True
Mauli
User
Beiträge: 10
Registriert: Donnerstag 5. April 2007, 09:03

Donnerstag 5. April 2007, 11:28

Habe dazu noch gefunden:

xreadlines()
Deprecated since release 2.3. Use "for line in file" instead.

Mit letzterem kann ich nicht viel anfangen da ich mit "for line in file" ja nicht steuern kann wieviele Zeilen er einlesen soll. Aber mein Problem hat sich wie gesagt bereits erledigt.
Zap
User
Beiträge: 533
Registriert: Freitag 13. Oktober 2006, 10:56

Donnerstag 5. April 2007, 11:41

BlackJack hat geschrieben:...
Die Methode ist nur aus Rückwärtskompatibilität noch vorhanden, für neuen Code ist sie überflüssig.

Code: Alles auswählen

...
In [4]: x is f
Out[4]: True
Ahso, danke für die Info. Und ich hatte mich schon gewundert wieso der type immer noch einfach 'file' war.

Der jens will mich heute wohl ärgern indem er einfach irgendwelche Funktionen
in den Raum rein wirft die im endeffekt doch nichts bringen *pfui* ;)
BlackJack

Donnerstag 5. April 2007, 11:46

@Mauli: Du kannst aber alle 5 Zeilen was mit den Ergebnissen machen und dann mit einer neuen Liste anfangen:

Code: Alles auswählen

    y = list()
    for line in f:
        y.append(float(line.split()[1]))
        if len(result) == 5:
            # Mach was mit den Werten…
            y = list()
Ich bin ja ein Fan von Funktionen die möglichst nur eine Sache machen und hätte Einlesen und Verarbeitung getrennt. Das Einlesen würde bei mir so aussehen:

Code: Alles auswählen

from itertools import islice

def iter_values(lines):
    iterator = iter(lines)
    while True:
        result = [float(line.split(None, 2)[1])
                  for line in islice(iterator, 5)]
        if not result:
            break
        yield result
Dieser Funktion ist egal wo die Zeilen herkommen und was mit den Werten passiert, sie konzentriert sich nur darauf maximal 5 Zeilen zu lesen und eine Liste mit Fliesskommazahlen zu liefern.
Mauli
User
Beiträge: 10
Registriert: Donnerstag 5. April 2007, 09:03

Donnerstag 5. April 2007, 13:14

Wirklich tolles Feedback in dem Forum :D und das obgleich mein Problem schon gelöst ist.

Zu Deinen Vorschlägen BlackJack:
Mit dem Trennen von Funktionen hast Du im Prinzip schon recht. Zur Zeit teste ich nur für ein wenig, das entgültige Programm wird / muss nachher sowie so anders aussehen.

Dein erstes Beispiel finde ich auch ganz interessant und entspricht meinen Vorstellungen.
Das zweite Beispiel kann ich (leider) schon nicht wirklich nachvollziehen, da ich mit Generatorfunktionen noch nix zu tun hatte. Aber sowie ich das sehe muss man da doch erst alle Linien einmal in den Speicher einlesen bevor man iter_values darauf loslassen kann. Und das ist nicht meine Absicht.
BlackJack

Donnerstag 5. April 2007, 15:42

Man muss nicht alle Zeilen einlesen. Die Funktion iteriert über das Objekt was übergeben wurde. Das kann eine Liste mit den Zeilen sein, Du kannst aber auch genausogut eine geöffnete Datei übergeben aus der nur die gerade benötigten Zeilen jeweils in 5er-Grüppchen gelesen werden.

Iteratoren bzw. Generator-Funktionen haben unter anderem den Vorteil, dass man Eingabe, Verarbeitung und Ausgabe in eigene Funktionen aufteilen kann, ohne dass die einzelnen Schritte immer komplett ausgeführt werden müssen bevor das Ergebnis an den nächsten Schritt weitergegeben wird.
Y0Gi
User
Beiträge: 1454
Registriert: Freitag 22. September 2006, 23:05
Wohnort: ja

Donnerstag 5. April 2007, 16:15

Der Iterator über dem Dateiobjekt hat den Vorteil, dass nicht alle Zeilen auf einmal gelesen werden, was bei deiner Zielmenge ja auch gut so ist.
Mauli hat geschrieben:Mit letzterem kann ich nicht viel anfangen da ich mit "for line in file" ja nicht steuern kann wieviele Zeilen er einlesen soll.
Doch. Folgendes kannst du machen:

Code: Alles auswählen

LINES_AT_ONCE = 5

# `f` ist das bereits geöffnete Dateiobjekt.

while True:
    lines = []
    i = 0
    for line in f:
        if i > LINES_AT_ONCE:
            break
        lines.append(line)
        i += 1
    do_stuff_with(lines)
Das lässt sich möglicherweise noch etwas weiter vereinfachen.
Antworten