¿Cómo leo dos líneas de un archivo a la vez usando Python?

82

Estoy codificando una secuencia de comandos de Python que analiza un archivo de texto. El formato de este archivo de texto es tal que cada elemento del archivo usa dos líneas y, por conveniencia, me gustaría leer ambas líneas antes de analizar. ¿Se puede hacer esto en Python?

Me gustaría algo como:

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

f.close

Pero esto rompe diciendo que:

ValueError: la mezcla de iteración y métodos de lectura perdería datos

Relacionado:

Daniel
fuente
8
Cambie f.readline () a f.next () y estará listo.
Paul
Consulte stackoverflow.com/questions/1528711/reading-lines-2-at-a-time para obtener más respuestas.
Foosion
@Paul ¿Sigue siendo válido este f.next ()? Recibo este error AttributeError: el objeto '_io.TextIOWrapper' no tiene atributo 'next'
SKR
1
@SKR en Python 3 tienes que hacer en su next(f)lugar.
Boris

Respuestas:

50

Pregunta similar aquí . No puede mezclar iteración y readline, por lo que debe usar uno u otro.

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

itertools.zip_longest() devuelve un iterador, por lo que funcionará bien incluso si el archivo tiene miles de millones de líneas.

Si hay un número impar de líneas, line2se establece enNone en la última iteración.

En Python2 necesitas usar izip_longesten su lugar.


En los comentarios, se preguntó si esta solución lee todo el archivo primero y luego itera sobre el archivo por segunda vez. Creo que no es así. La with open('a') as flínea abre un identificador de archivo, pero no lee el archivo. fes un iterador, por lo que su contenido no se lee hasta que se solicita. zip_longesttoma iteradores como argumentos y devuelve un iterador.

zip_longestde hecho, se alimenta con el mismo iterador, f, dos veces. Pero lo que termina sucediendo es que next(f)se invoca en el primer argumento y luego en el segundo argumento. Dado que next()se llama en el mismo iterador subyacente, se generan líneas sucesivas. Esto es muy diferente a leer todo el archivo. De hecho, el propósito de usar iteradores es precisamente evitar leer todo el archivo.

Por lo tanto, creo que la solución funciona como se desea: el bucle for solo lee el archivo una vez.

Para corroborar esto, ejecuté la solución zip_longest frente a una solución que usa f.readlines(). Puse un input()al final para pausar los scripts y ejecuté ps axuwcada uno:

% 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

Se readlineslee claramente en todo el archivo a la vez. Dado que zip_longest_methodutiliza mucha menos memoria, creo que es seguro concluir que no está leyendo todo el archivo a la vez.

unutbu
fuente
6
Me gusta (*[f]*2)porque muestra que puede obtener fragmentos de cualquier tamaño que desee simplemente cambiando el número (por lo que no editaré la respuesta para cambiarla), pero en este caso (f, f)probablemente sea más fácil de escribir.
Steve Losh
si usa en lineslugar de line1, line2entonces solo necesita cambiar un número ( 2) para leer nlíneas a la vez.
jfs
27

utilizar next(), por ejemplo

with open("file") as f:
    for line in f:
        print(line)
        nextline = next(f)
        print("next line", nextline)
        ....
ghostdog74
fuente
1
como señala RedGlyph en su versión de esta respuesta, un número impar de líneas resultará en un StopIterationaumento.
drevicko
2
next () ahora admite un argumento predeterminado para evitar la excepción:nextline = next(f,None)
gerardw
11

Procedería de manera similar a ghostdog74 , solo que con la prueba externa y algunas modificaciones:

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

Esto mantiene el código simple pero robusto. Utilizando elwith cierra el archivo si sucede algo más, o simplemente cierra los recursos una vez que lo haya agotado y salga del ciclo.

Tenga en cuenta que withnecesita 2.6 o 2.5 con la with_statementfunción habilitada.

RedGlyph
fuente
8

¿Qué tal este, si alguien ve un problema con él?

with open('file_name') as f:
    for line1, line2 in zip(f, f):
        print(line1, line2)
svural
fuente
1
Esto descartará la última línea si su archivo tiene un número impar de líneas. Lo bueno es que puede extender esto para leer 3 líneas a la vez con for l1, l2, l3 in zip(f, f, f):y así sucesivamente; de nuevo, las últimas 1 o 2 líneas se descartarán si el número de líneas no es divisible por 3.
Boris
4

Funciona para archivos pares e impares. Simplemente ignora la última línea incomparable.

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

Si tiene archivos grandes, este no es el enfoque correcto. Está cargando todo el archivo en la memoria con readlines (). Una vez escribí una clase que leyó el archivo guardando la posición fseek de cada inicio de línea. Esto le permite obtener líneas específicas sin tener todo el archivo en la memoria, y también puede avanzar y retroceder.

Lo pego aquí. La licencia es de dominio público, es decir, haz lo que quieras con ella. Tenga en cuenta que esta clase se escribió hace 6 años y no la he tocado ni revisado desde entonces. Creo que ni siquiera es compatible con archivos. Caveat emptor . Además, tenga en cuenta que esto es excesivo para su problema. No estoy diciendo que definitivamente deba ir por este camino, pero tenía este código y disfruto compartirlo si necesita un acceso más complejo.

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
fuente
Es posible que desee utilizar itertools.izip () en su lugar, ¡especialmente para archivos grandes!
RedGlyph
Incluso con izip, dividir la lista de esa manera lo llevará todo a la memoria.
Steve Losh
En realidad, la readlines()llamada también llevará todo a la memoria.
Steve Losh
No me gusta tu clase. Está iterando dos veces sobre todo el archivo mientras inicializa el archivo. Para archivos grandes con líneas cortas, la memoria guardada no es mucha.
Georg Schölly
@Steve: sí, lamentablemente. Pero zip agregaría una capa adicional en la memoria creando la lista completa de tuplas (a menos que sea Python 3), donde izip generaría las tuplas una a la vez. Creo que eso es lo que quisiste decir, pero prefiero aclarar mi comentario anterior de todos modos :-)
RedGlyph
3
file_name = 'your_file_name'
file_open = open (nombre_archivo, 'r')

manejador def (line_one, line_two):
    imprimir (línea_una, línea_dos)

while file_open:
    tratar:
        uno = file_open.next ()
        dos = file_open.next () 
        manejador (uno, dos)
    excepto (StopIteration):
        file_open.close ()
        romper
Martin P. Hellwig
fuente
1
while file_open:es engañoso debido a que es equivalente a while True:en este caso.
jfs
Lo cual es intencionalmente, aunque estoy de acuerdo en que se puede argumentar que es más limpio hacer 'while True', lo que indica que necesita un descanso para salir del ciclo. Elegí no hacerlo porque creo (nuevamente discutible) que se lee mejor de esta manera, sin dejar ninguna duda sobre cuánto tiempo debe permanecer abierto el archivo y qué hacer con él mientras tanto. Sin embargo, la mayoría de las veces también haría 'while True' para mí.
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
fuente
1

Mi idea es crear un generador que lea dos líneas del archivo a la vez y lo devuelva como 2 tuplas. Esto significa que luego puede iterar sobre los resultados.

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

Si tiene un número impar de líneas, no funcionará perfectamente, pero esto debería darle un buen esquema.

Simón Callan
fuente
1

Trabajé en un problema similar el mes pasado. Probé un bucle while con f.readline () y f.readlines (). Mi archivo de datos no es enorme, así que finalmente elegí f.readlines (), lo que me da más control del índice, de lo contrario tengo que usar f.seek () para mover hacia adelante y hacia atrás el puntero del archivo.

Mi caso es más complicado que OP. Debido a que mi archivo de datos es más flexible en cuanto a cuántas líneas se analizarán cada vez, tengo que verificar algunas condiciones antes de poder analizar los datos.

Otro problema que descubrí sobre f.seek () es que no maneja muy bien utf-8 cuando uso codecs.open ('', 'r', 'utf-8'), (no estoy exactamente seguro de la culpable, finalmente renuncié a este enfoque).

Valle arbolado
fuente
1

Pequeño lector sencillo. Extraerá líneas en pares de dos y las devolverá como una tupla a medida que itera sobre el objeto. Puede cerrarlo manualmente o se cerrará solo cuando quede fuera de alcance.

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

f.close

Ahora mismo, puede leer el archivo cada dos líneas. Si lo desea, también puede verificar el estado f antesf.next()

Kimmi
fuente
0

Si el archivo tiene un tamaño razonable, otro enfoque que usa la comprensión de listas para leer el archivo completo en una lista de 2 tuplas es este:

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
fuente
-2

Este código de Python imprimirá las dos primeras líneas:

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