Wie lese ich mit Python zwei Zeilen gleichzeitig aus einer Datei?

80

Ich codiere ein Python-Skript, das eine Textdatei analysiert. Das Format dieser Textdatei ist so, dass jedes Element in der Datei zwei Zeilen verwendet. Der Einfachheit halber möchte ich beide Zeilen vor dem Parsen lesen. Kann das in Python gemacht werden?

Ich möchte etwas wie:

f = open(filename, "r")
for line in f:
    line1 = line
    line2 = f.readline()

f.close

Aber das bricht und sagt:

ValueError: Das Mischen von Iterations- und Lesemethoden würde Daten verlieren

Verbunden:

Daniel
quelle
8
Ändern Sie f.readline () in f.next () und Sie sind fertig.
Paul
Weitere Antworten finden Sie unter stackoverflow.com/questions/1528711/reading-lines-2-at-a-time .
Foosion
@Paul Ist diese f.next () noch gültig? Ich erhalte diesen Fehler AttributeError: '_io.TextIOWrapper' Objekt hat kein Attribut 'next'
SKR
1
@SKR auf Python 3 müssen Sie next(f)stattdessen tun .
Boris

Antworten:

50

Ähnliche Frage hier . Sie können Iteration und Readline nicht mischen, daher müssen Sie die eine oder andere verwenden.

while True:
    line1 = f.readline()
    line2 = f.readline()
    if not line2: break  # EOF
    ...
Robince
quelle
47
import itertools
with open('a') as f:
    for line1,line2 in itertools.zip_longest(*[f]*2):
        print(line1,line2)

itertools.zip_longest() Gibt einen Iterator zurück, sodass er auch dann gut funktioniert, wenn die Datei Milliarden Zeilen lang ist.

Wenn es eine ungerade Anzahl von Zeilen gibt, line2wird diese Nonebei der letzten Iteration auf gesetzt.

Auf Python2 müssen Sie izip_longeststattdessen verwenden.


In den Kommentaren wurde gefragt, ob diese Lösung zuerst die gesamte Datei liest und dann ein zweites Mal über die Datei iteriert. Ich glaube das nicht. Die with open('a') as fZeile öffnet ein Dateihandle, liest die Datei jedoch nicht. fist ein Iterator, daher wird sein Inhalt erst gelesen, wenn er angefordert wird. zip_longestNimmt Iteratoren als Argumente und gibt einen Iterator zurück.

zip_longestwird tatsächlich zweimal mit demselben Iterator f gefüttert. Aber was am Ende passiert, ist, dass next(f)beim ersten Argument und dann beim zweiten Argument aufgerufen wird. Da next()auf demselben zugrunde liegenden Iterator aufgerufen wird, werden aufeinanderfolgende Zeilen ausgegeben. Dies unterscheidet sich stark vom Einlesen der gesamten Datei. In der Tat besteht der Zweck der Verwendung von Iteratoren genau darin, das Einlesen der gesamten Datei zu vermeiden.

Ich glaube daher, dass die Lösung wie gewünscht funktioniert - die Datei wird von der for-Schleife nur einmal gelesen.

Um dies zu bestätigen, habe ich die Lösung zip_longest im Vergleich zu einer Lösung mit ausgeführt f.readlines(). Ich habe input()am Ende ein gesetzt, um die Skripte anzuhalten, und habe ps axuwjedes ausgeführt:

% ps axuw | grep zip_longest_method.py

unutbu 11119 2.2 0.2 4520 2712 pts/0 S+ 21:14 0:00 python /home/unutbu/pybin/zip_longest_method.py bigfile

% ps axuw | grep readlines_method.py

unutbu 11317 6.5 8.8 93908 91680 pts/0 S+ 21:16 0:00 python /home/unutbu/pybin/readlines_method.py bigfile

Das readlinesliest deutlich die ganze Datei auf einmal ein. Da der zip_longest_methodSpeicher viel weniger Speicher benötigt, kann ich mit Sicherheit den Schluss ziehen, dass nicht die gesamte Datei auf einmal gelesen wird.

unutbu
quelle
6
Ich mag das, (*[f]*2)weil es zeigt, dass Sie Blöcke beliebiger Größe erhalten können, indem Sie einfach die Nummer ändern (damit ich die Antwort nicht bearbeite, um sie zu ändern), aber in diesem Fall (f, f)ist es wahrscheinlich einfacher zu tippen.
Steve Losh
Wenn Sie linesstattdessen verwenden, müssen line1, line2Sie nur eine Zahl ( 2) ändern , um jeweils nZeilen zu lesen .
JFS
26

verwenden next(), z

with open("file") as f:
    for line in f:
        print(line)
        nextline = next(f)
        print("next line", nextline)
        ....
Ghostdog74
quelle
1
Wie RedGlyph in seiner Version dieser Antwort hervorhebt, führt eine ungerade Anzahl von Zeilen zu einer StopIterationErhöhung.
Drevicko
2
next () unterstützt jetzt ein Standardargument, um die Ausnahme zu vermeiden:nextline = next(f,None)
gerardw
11

Ich würde ähnlich wie Ghostdog74 vorgehen , nur mit dem Versuch draußen und ein paar Modifikationen:

try:
    with open(filename) as f:
        for line1 in f:
            line2 = f.next()
            # process line1 and line2 here
except StopIteration:
    print "(End)" # do whatever you need to do with line1 alone

Dies hält den Code einfach und dennoch robust. Mit der withOption wird die Datei geschlossen, wenn etwas anderes passiert, oder die Ressourcen werden einfach geschlossen, sobald Sie sie erschöpft haben und die Schleife verlassen.

Beachten Sie, dass withbei with_statementaktivierter Funktion 2.6 oder 2.5 erforderlich ist .

RedGlyph
quelle
8

Wie wäre es mit diesem, wenn jemand ein Problem damit sieht?

with open('file_name') as f:
    for line1, line2 in zip(f, f):
        print(line1, line2)
svural
quelle
1
Dadurch wird die letzte Zeile verworfen, wenn Ihre Datei eine ungerade Anzahl von Zeilen enthält. Das Schöne ist, dass Sie dies erweitern können, um 3 Zeilen gleichzeitig mit for l1, l2, l3 in zip(f, f, f):und so weiter zu lesen . Auch hier werden die letzten 1 oder 2 Zeilen verworfen, wenn die Anzahl der Zeilen nicht durch 3 teilbar ist.
Boris
4

Funktioniert für Dateien mit gerader und ungerader Länge. Die nicht übereinstimmende letzte Zeile wird einfach ignoriert.

f=file("file")

lines = f.readlines()
for even, odd in zip(lines[0::2], lines[1::2]):
    print "even : ", even
    print "odd : ", odd
    print "end cycle"
f.close()

Wenn Sie große Dateien haben, ist dies nicht der richtige Ansatz. Sie laden die gesamte Datei mit readlines () in den Speicher. Ich habe einmal eine Klasse geschrieben, die die Datei las und die fseek-Position jedes Zeilenanfangs speicherte. Auf diese Weise können Sie bestimmte Zeilen abrufen, ohne die gesamte Datei im Speicher zu haben, und Sie können auch vorwärts und rückwärts gehen.

Ich füge es hier ein. Die Lizenz ist gemeinfrei, dh machen Sie, was Sie wollen. Bitte beachten Sie, dass diese Klasse vor 6 Jahren geschrieben wurde und ich sie seitdem nicht mehr berührt oder überprüft habe. Ich denke, es ist nicht einmal dateikonform. Vorbehalt Emptor . Beachten Sie auch, dass dies für Ihr Problem übertrieben ist. Ich behaupte nicht, dass Sie auf jeden Fall diesen Weg gehen sollten, aber ich hatte diesen Code und teile ihn gerne, wenn Sie einen komplexeren Zugriff benötigen.

import string
import re

class FileReader:
    """ 
    Similar to file class, but allows to access smoothly the lines 
    as when using readlines(), with no memory payload, going back and forth,
    finding regexps and so on.
    """
    def __init__(self,filename): # fold>>
        self.__file=file(filename,"r")
        self.__currentPos=-1
        # get file length
        self.__file.seek(0,0)
        counter=0
        line=self.__file.readline()
        while line != '':
            counter = counter + 1
            line=self.__file.readline()
        self.__length = counter
        # collect an index of filedescriptor positions against
        # the line number, to enhance search
        self.__file.seek(0,0)
        self.__lineToFseek = []

        while True:
            cur=self.__file.tell()
            line=self.__file.readline()
            # if it's not null the cur is valid for
            # identifying a line, so store
            self.__lineToFseek.append(cur)
            if line == '':
                break
    # <<fold
    def __len__(self): # fold>>
        """
        member function for the operator len()
        returns the file length
        FIXME: better get it once when opening file
        """
        return self.__length
        # <<fold
    def __getitem__(self,key): # fold>>
        """ 
        gives the "key" line. The syntax is

        import FileReader
        f=FileReader.FileReader("a_file")
        line=f[2]

        to get the second line from the file. The internal
        pointer is set to the key line
        """

        mylen = self.__len__()
        if key < 0:
            self.__currentPos = -1
            return ''
        elif key > mylen:
            self.__currentPos = mylen
            return ''

        self.__file.seek(self.__lineToFseek[key],0)
        counter=0
        line = self.__file.readline()
        self.__currentPos = key
        return line
        # <<fold
    def next(self): # fold>>
        if self.isAtEOF():
            raise StopIteration
        return self.readline()
    # <<fold
    def __iter__(self): # fold>>
        return self
    # <<fold
    def readline(self): # fold>>
        """
        read a line forward from the current cursor position.
        returns the line or an empty string when at EOF
        """
        return self.__getitem__(self.__currentPos+1)
        # <<fold
    def readbackline(self): # fold>>
        """
        read a line backward from the current cursor position.
        returns the line or an empty string when at Beginning of
        file.
        """
        return self.__getitem__(self.__currentPos-1)
        # <<fold
    def currentLine(self): # fold>>
        """
        gives the line at the current cursor position
        """
        return self.__getitem__(self.__currentPos)
        # <<fold
    def currentPos(self): # fold>>
        """ 
        return the current position (line) in the file
        or -1 if the cursor is at the beginning of the file
        or len(self) if it's at the end of file
        """
        return self.__currentPos
        # <<fold
    def toBOF(self): # fold>>
        """
        go to beginning of file
        """
        self.__getitem__(-1)
        # <<fold
    def toEOF(self): # fold>>
        """
        go to end of file
        """
        self.__getitem__(self.__len__())
        # <<fold
    def toPos(self,key): # fold>>
        """
        go to the specified line
        """
        self.__getitem__(key)
        # <<fold
    def isAtEOF(self): # fold>>
        return self.__currentPos == self.__len__()
        # <<fold
    def isAtBOF(self): # fold>>
        return self.__currentPos == -1
        # <<fold
    def isAtPos(self,key): # fold>>
        return self.__currentPos == key
        # <<fold

    def findString(self, thestring, count=1, backward=0): # fold>>
        """
        find the count occurrence of the string str in the file
        and return the line catched. The internal cursor is placed
        at the same line.
        backward is the searching flow.
        For example, to search for the first occurrence of "hello
        starting from the beginning of the file do:

        import FileReader
        f=FileReader.FileReader("a_file")
        f.toBOF()
        f.findString("hello",1,0)

        To search the second occurrence string from the end of the
        file in backward movement do:

        f.toEOF()
        f.findString("hello",2,1)

        to search the first occurrence from a given (or current) position
        say line 150, going forward in the file 

        f.toPos(150)
        f.findString("hello",1,0)

        return the string where the occurrence is found, or an empty string
        if nothing is found. The internal counter is placed at the corresponding
        line number, if the string was found. In other case, it's set at BOF
        if the search was backward, and at EOF if the search was forward.

        NB: the current line is never evaluated. This is a feature, since
        we can so traverse occurrences with a

        line=f.findString("hello")
        while line == '':
            line.findString("hello")

        instead of playing with a readline every time to skip the current
        line.
        """
        internalcounter=1
        if count < 1:
            count = 1
        while 1:
            if backward == 0:
                line=self.readline()
            else:
                line=self.readbackline()

            if line == '':
                return ''
            if string.find(line,thestring) != -1 :
                if count == internalcounter:
                    return line
                else:
                    internalcounter = internalcounter + 1
                    # <<fold
    def findRegexp(self, theregexp, count=1, backward=0): # fold>>
        """
        find the count occurrence of the regexp in the file
        and return the line catched. The internal cursor is placed
        at the same line.
        backward is the searching flow.
        You need to pass a regexp string as theregexp.
        returns a tuple. The fist element is the matched line. The subsequent elements
        contains the matched groups, if any.
        If no match returns None
        """
        rx=re.compile(theregexp)
        internalcounter=1
        if count < 1:
            count = 1
        while 1:
            if backward == 0:
                line=self.readline()
            else:
                line=self.readbackline()

            if line == '':
                return None
            m=rx.search(line)
            if m != None :
                if count == internalcounter:
                    return (line,)+m.groups()
                else:
                    internalcounter = internalcounter + 1
    # <<fold
    def skipLines(self,key): # fold>>
        """
        skip a given number of lines. Key can be negative to skip
        backward. Return the last line read.
        Please note that skipLines(1) is equivalent to readline()
        skipLines(-1) is equivalent to readbackline() and skipLines(0)
        is equivalent to currentLine()
        """
        return self.__getitem__(self.__currentPos+key)
    # <<fold
    def occurrences(self,thestring,backward=0): # fold>>
        """
        count how many occurrences of str are found from the current
        position (current line excluded... see skipLines()) to the
        begin (or end) of file.
        returns a list of positions where each occurrence is found,
        in the same order found reading the file.
        Leaves unaltered the cursor position.
        """
        curpos=self.currentPos()
        list = []
        line = self.findString(thestring,1,backward)
        while line != '':
            list.append(self.currentPos())
            line = self.findString(thestring,1,backward)
        self.toPos(curpos)
        return list
        # <<fold
    def close(self): # fold>>
        self.__file.close()
    # <<fold
Stefano Borini
quelle
Möglicherweise möchten Sie stattdessen itertools.izip () verwenden, insbesondere für große Dateien!
RedGlyph
Selbst mit izip wird durch das Aufteilen der Liste alles in den Speicher gezogen.
Steve Losh
Tatsächlich wird der readlines()Anruf auch alles in den Speicher ziehen.
Steve Losh
Ich mag deine Klasse nicht. Sie iterieren zweimal über die gesamte Datei, während Sie die Datei initialisieren. Bei großen Dateien mit kurzen Zeilen ist der gespeicherte Speicher nicht viel.
Georg Schölly
@ Steve: Ja, leider genug. Zip fügt dem Speicher jedoch eine zusätzliche Ebene hinzu, indem die gesamte Liste der Tupel erstellt wird (es sei denn, es handelt sich um Python 3), wobei izip die Tupel einzeln generiert. Ich denke, das haben Sie gemeint, aber ich möchte meinen vorherigen Kommentar trotzdem klarstellen :-)
RedGlyph
3
Dateiname = 'Ihr_Dateiname'
file_open = open (Dateiname, 'r')

def handler (line_one, line_two):
    print (line_one, line_two)

während file_open:
    Versuchen:
        one = file_open.next ()
        two = file_open.next () 
        Handler (eins, zwei)
    außer (StopIteration):
        file_open.close ()
        brechen
Martin P. Hellwig
quelle
1
while file_open:ist irreführend, weil es while True:in diesem Fall gleichwertig ist.
JFS
Das ist absichtlich so, obwohl ich damit einverstanden bin, dass es wohl sauberer ist, "while True" zu machen, was darauf hinweist, dass Sie eine Pause brauchen, um aus der Schleife herauszukommen. Ich habe mich entschieden, es nicht zu tun, weil ich glaube (wieder fraglich), dass es auf diese Weise besser liest, ohne Zweifel darüber, wie lange die Datei geöffnet bleiben muss und was in der Zwischenzeit damit zu tun ist. Die meiste Zeit würde ich aber auch "while True" für mich tun.
Martin P. Hellwig
2
def readnumlines(file, num=2):
    f = iter(file)
    while True:
        lines = [None] * num
        for i in range(num):
            try:
                lines[i] = f.next()
            except StopIteration: # EOF or not enough lines available
                return
        yield lines

# use like this
f = open("thefile.txt", "r")
for line1, line2 in readnumlines(f):
    # do something with line1 and line2

# or
for line1, line2, line3, ..., lineN in readnumlines(f, N):
    # do something with N lines
Georg Schölly
quelle
1

Meine Idee ist es, einen Generator zu erstellen, der zwei Zeilen gleichzeitig aus der Datei liest und dies als 2-Tupel zurückgibt. Dies bedeutet, dass Sie dann über die Ergebnisse iterieren können.

from cStringIO import StringIO

def read_2_lines(src):   
    while True:
        line1 = src.readline()
        if not line1: break
        line2 = src.readline()
        if not line2: break
        yield (line1, line2)


data = StringIO("line1\nline2\nline3\nline4\n")
for read in read_2_lines(data):
    print read

Wenn Sie eine ungerade Anzahl von Zeilen haben, funktioniert dies nicht perfekt, aber dies sollte Ihnen einen guten Überblick geben.

Simon Callan
quelle
1

Ich habe letzten Monat an einem ähnlichen Problem gearbeitet. Ich habe eine while-Schleife mit f.readline () sowie f.readlines () versucht. Meine Datendatei ist nicht riesig, also habe ich mich schließlich für f.readlines () entschieden, wodurch ich mehr Kontrolle über den Index habe. Andernfalls muss ich f.seek () verwenden, um den Dateizeiger hin und her zu bewegen.

Mein Fall ist komplizierter als OP. Da meine Datendatei flexibler ist, wie viele Zeilen jedes Mal analysiert werden sollen, muss ich einige Bedingungen überprüfen, bevor ich die Daten analysieren kann.

Ein weiteres Problem, das ich bei f.seek () herausgefunden habe, ist, dass es utf-8 nicht sehr gut verarbeitet, wenn ich codecs.open ('', 'r', 'utf-8') verwende (nicht genau sicher, ob.) Schuldiger, schließlich habe ich diesen Ansatz aufgegeben.)

Dingle
quelle
1

Einfacher kleiner Leser. Es werden Linien in Zweierpaaren gezogen und als Tupel zurückgegeben, wenn Sie über das Objekt iterieren. Sie können es manuell schließen, oder es schließt sich von selbst, wenn es außerhalb des Gültigkeitsbereichs liegt.

class doublereader:
    def __init__(self,filename):
        self.f = open(filename, 'r')
    def __iter__(self):
        return self
    def next(self):
        return self.f.next(), self.f.next()
    def close(self):
        if not self.f.closed:
            self.f.close()
    def __del__(self):
        self.close()

#example usage one
r = doublereader(r"C:\file.txt")
for a, h in r:
    print "x:%s\ny:%s" % (a,h)
r.close()

#example usage two
for x,y in doublereader(r"C:\file.txt"):
    print "x:%s\ny:%s" % (x,y)
#closes itself as soon as the loop goes out of scope
Bo Buchanan
quelle
1
f = open(filename, "r")
for line in f:
    line1 = line
    f.next()

f.close

Im Moment können Sie die Datei alle zwei Zeilen lesen. Wenn Sie möchten, können Sie auch vorher den f-Status überprüfenf.next()

Kimmi
quelle
0

Wenn die Datei eine angemessene Größe hat, ist ein anderer Ansatz, bei dem das Listenverständnis verwendet wird , um die gesamte Datei in eine Liste mit 2 Tupeln einzulesen :

filaname = '/path/to/file/name'

with open(filename, 'r') as f:
    list_of_2tuples = [ (line,f.readline()) for line in f ]

for (line1,line2) in list_of_2tuples: # Work with them in pairs.
    print('%s :: %s', (line1,line2))
NYCeyes
quelle
-2

Dieser Python-Code druckt die ersten beiden Zeilen:

import linecache  
filename = "ooxx.txt"  
print(linecache.getline(filename,2))
Timothy.hmchen
quelle