Jak odczytać jednocześnie dwa wiersze z pliku przy użyciu języka Python

82

Koduję skrypt w języku Python, który analizuje plik tekstowy. Format tego pliku tekstowego jest taki, że każdy element w pliku wykorzystuje dwa wiersze i dla wygody chciałbym przeczytać oba wiersze przed analizą. Czy można to zrobić w Pythonie?

Chciałbym coś takiego:

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

f.close

Ale to przerywa, mówiąc, że:

ValueError: Mieszanie metod iteracji i odczytu spowodowałoby utratę danych

Związane z:

Daniel
źródło
8
Zmień f.readline () na f.next () i gotowe.
Paul
Więcej odpowiedzi znajdziesz na stackoverflow.com/questions/1528711/reading-lines-2-at-a-time .
foosion
@Paul Czy to f.next () jest nadal ważne? Otrzymuję ten błąd AttributeError: Obiekt „_io.TextIOWrapper” nie ma atrybutu „next”
SKR
1
@SKR w Pythonie 3 musisz next(f)zamiast tego zrobić .
Boris

Odpowiedzi:

50

Podobne pytanie tutaj . Nie możesz mieszać iteracji i readline, więc musisz użyć jednego lub drugiego.

while True:
    line1 = f.readline()
    line2 = f.readline()
    if not line2: break  # EOF
    ...
Robince
źródło
48
import itertools
with open('a') as f:
    for line1,line2 in itertools.zip_longest(*[f]*2):
        print(line1,line2)

itertools.zip_longest() zwraca iterator, więc będzie działał dobrze, nawet jeśli plik ma miliardy linii.

Jeśli istnieje nieparzysta liczba linii, to line2jest ustawiana Nonena ostatnią iterację.

W Pythonie2 musisz użyć izip_longestzamiast tego.


W komentarzach zadano pytanie, czy to rozwiązanie najpierw odczytuje cały plik, a następnie wykonuje iterację po pliku po raz drugi. Myślę, że tak nie jest. with open('a') as fLinia otwiera uchwyt pliku, ale nie odczytuje plik. fjest iteratorem, więc jego zawartość jest czytana dopiero po zażądaniu. zip_longestprzyjmuje iteratory jako argumenty i zwraca iterator.

zip_longestjest rzeczywiście podawany dwukrotnie tym samym iteratorem, f. Ale to, co się ostatecznie dzieje, next(f)jest wywoływane przy pierwszym argumencie, a następnie przy drugim argumencie. Ponieważ next()jest wywoływany na tym samym podstawowym iteratorze, otrzymywane są kolejne linie. To bardzo różni się od czytania całego pliku. W rzeczywistości celem używania iteratorów jest właśnie uniknięcie czytania całego pliku.

Dlatego uważam, że rozwiązanie działa zgodnie z oczekiwaniami - plik jest odczytywany tylko raz przez pętlę for.

Aby to potwierdzić, uruchomiłem rozwiązanie zip_longest w porównaniu z rozwiązaniem wykorzystującym f.readlines(). Umieściłem input()na końcu, aby wstrzymać skrypty, i uruchomiłem ps axuwna każdym:

% 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

readlinesWyraźnie odczytuje cały plik na raz. Ponieważ zip_longest_methodzużywa znacznie mniej pamięci, myślę, że można bezpiecznie stwierdzić, że nie czyta całego pliku na raz.

unutbu
źródło
6
Podoba mi się, (*[f]*2)ponieważ pokazuje, że możesz uzyskać dowolne fragmenty rozmiaru, po prostu zmieniając liczbę (więc nie będę edytować odpowiedzi, aby ją zmienić), ale w tym przypadku (f, f)prawdopodobnie łatwiej jest wpisać.
Steve Losh
jeśli używasz lineszamiast line1, line2tego, wystarczy zmienić jedną liczbę ( 2), aby odczytywać nwiersze na raz.
jfs
27

używać next()np

with open("file") as f:
    for line in f:
        print(line)
        nextline = next(f)
        print("next line", nextline)
        ....
ghostdog74
źródło
1
jak wskazuje RedGlyph w swojej wersji tej odpowiedzi, nieparzysta liczba linii spowoduje StopIterationpodniesienie.
drevicko
2
nextline = next(f,None)
Funkcja
11

Postąpiłbym podobnie jak ghostdog74 , tylko z próbą na zewnątrz i kilkoma modyfikacjami:

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

Dzięki temu kod jest prosty, a jednocześnie solidny. Użycie polecenia withzamyka plik, jeśli dzieje się coś innego, lub po prostu zamyka zasoby po ich wyczerpaniu i wyjdzie z pętli.

Zwróć uwagę, że withwymaga wersji 2.6 lub 2.5 z with_statementwłączoną funkcją.

RedGlyph
źródło
8

co powiesz na ten, ktokolwiek widzi z nim problem

with open('file_name') as f:
    for line1, line2 in zip(f, f):
        print(line1, line2)
svural
źródło
1
Spowoduje to odrzucenie ostatniej linii, jeśli plik ma nieparzystą liczbę linii. Fajną rzeczą jest to, że możesz to rozszerzyć, aby odczytać 3 wiersze naraz za pomocą for l1, l2, l3 in zip(f, f, f):i tak dalej; ponownie, ostatnia 1 lub 2 linie zostaną odrzucone, jeśli liczba linii nie jest podzielna przez 3.
Boris
4

Działa dla plików parzystych i nieparzystych. Po prostu ignoruje niedopasowaną ostatnią linię.

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()

Jeśli masz duże pliki, nie jest to właściwe podejście. Ładujesz cały plik do pamięci za pomocą readlines (). Kiedyś napisałem klasę, która czytała plik, zapisując pozycję fseek każdego początku wiersza. Pozwala to na pobranie określonych wierszy bez konieczności przechowywania całego pliku w pamięci, a także umożliwia przechodzenie do przodu i do tyłu.

Wklejam to tutaj. Licencja jest domeną publiczną, co oznacza, rób z nią, co chcesz. Zwróć uwagę, że ta klasa została napisana 6 lat temu i od tamtej pory jej nie dotykałem ani nie sprawdzałem. Myślę, że nie jest to nawet zgodne z plikami. Caveat emptor . Pamiętaj też, że jest to przesada dla twojego problemu. Nie twierdzę, że zdecydowanie powinieneś iść tą drogą, ale miałem ten kod i lubię się nim dzielić, jeśli potrzebujesz bardziej złożonego dostępu.

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
źródło
Możesz zamiast tego użyć itertools.izip (), szczególnie w przypadku dużych plików!
RedGlyph
Nawet z izipem, pokrojenie listy w ten sposób zapisze wszystko w pamięci.
Steve Losh
Właściwie to readlines()wezwanie również zapisze wszystko w pamięci.
Steve Losh
Nie lubię twojej klasy. Podczas inicjalizacji pliku wykonujesz dwa razy iterację w całym pliku. W przypadku dużych plików z krótkimi wierszami zaoszczędzonej pamięci nie jest dużo.
Georg Schölly
@Steve: tak, niestety. Ale zip dodałby dodatkową warstwę do pamięci, tworząc całą listę krotek (chyba że jest to Python 3), gdzie izip generowałby krotki pojedynczo. Myślę, że to miałeś na myśli, ale i tak wolałbym wyjaśnić mój poprzedni komentarz :-)
RedGlyph
3
file_name = 'twoja_nazwa_pliku'
file_open = open (nazwa_pliku, 'r')

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

while file_open:
    próbować:
        one = file_open.next ()
        two = file_open.next () 
        przewodnik (jeden, dwa)
    z wyjątkiem (StopIteration):
        file_open.close ()
        przerwa
Martin P. Hellwig
źródło
1
while file_open:wprowadza w błąd, ponieważ jest równoważne while True:w tym przypadku.
jfs
Co jest celowe, chociaż zgadzam się, że można dyskutować, czy jest to czystsze, aby zrobić „while True”, wskazując, że potrzebujesz przerwy, aby wyjść z pętli. Zdecydowałem się tego nie robić, ponieważ uważam (ponownie dyskusyjne), że w ten sposób czyta się ładniej, nie pozostawiając wątpliwości, jak długo plik musi pozostać otwarty i co z nim zrobić w międzyczasie. Jednak przez większość czasu robiłem „podczas gdy True” też dla siebie.
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
źródło
1

Moim pomysłem jest stworzenie generatora, który odczytuje dwa wiersze z pliku na raz i zwraca je jako dwie krotki, co oznacza, że ​​możesz następnie iterować po wynikach.

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

Jeśli masz nieparzystą liczbę linii, nie będzie to działać idealnie, ale powinno to dać dobry zarys.

Simon Callan
źródło
1

Pracowałem nad podobnym problemem w zeszłym miesiącu. Próbowałem zrobić pętlę while z f.readline () oraz f.readlines (). Mój plik danych nie jest duży, więc w końcu wybrałem f.readlines (), co daje mi większą kontrolę nad indeksem, w przeciwnym razie muszę używać f.seek (), aby przesuwać wskaźnik pliku w tę iz powrotem.

Moja sprawa jest bardziej skomplikowana niż OP. Ponieważ mój plik danych jest bardziej elastyczny, jeśli chodzi o liczbę wierszy do przeanalizowania za każdym razem, muszę sprawdzić kilka warunków, zanim będę mógł przeanalizować dane.

Innym problemem, o którym dowiedziałem się o f.seek (), jest to, że nie obsługuje on zbyt dobrze utf-8, gdy używam codecs.open ('', 'r', 'utf-8'), (nie jestem pewien co do sprawca, ostatecznie zrezygnowałem z tego podejścia).

Dingle
źródło
1

Prosty mały czytelnik. Pociągnie linie w pary po dwie i zwróci je jako krotkę, gdy będziesz iterować po obiekcie. Możesz zamknąć go ręcznie lub zamknie się samoczynnie, gdy znajdzie się poza zakresem.

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
źródło
1
f = open(filename, "r")
for line in f:
    line1 = line
    f.next()

f.close

Teraz możesz czytać plik co dwie linie. Jeśli chcesz, możesz również wcześniej sprawdzić stan ff.next()

Kimmi
źródło
0

Jeśli plik ma rozsądny rozmiar, inne podejście, które wykorzystuje rozumienie list do wczytania całego pliku do listy 2 krotek , jest takie:

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
źródło
-2

Ten kod Pythona wypisze pierwsze dwie linie:

import linecache  
filename = "ooxx.txt"  
print(linecache.getline(filename,2))
Timothy.hmchen
źródło