¿Cuál es la forma más eficiente de obtener la primera y la última línea de un archivo de texto?

Tengo un archivo de texto que contiene una marca de tiempo en cada línea. Mi objective es encontrar el rango de tiempo. Todas las horas están en orden, por lo que la primera línea será la primera y la última será la última. Solo necesito la primera y la última línea. ¿Cuál sería la forma más eficiente de obtener estas líneas en python?

Nota: estos archivos tienen una longitud relativamente grande, aproximadamente 1-2 millones de líneas cada uno y tengo que hacer esto para varios cientos de archivos.

docs para el módulo io

with open(fname, 'rb') as fh: first = next(fh).decode() fh.seek(-1024, 2) last = fh.readlines()[-1].decode() 

El valor variable aquí es 1024: representa la longitud de cadena promedio. Elijo 1024 solo por ejemplo. Si tiene una estimación del promedio de la longitud de la línea, podría usar ese valor por 2.

Como no tiene idea alguna sobre el posible límite superior para la longitud de la línea, la solución obvia sería realizar un bucle sobre el archivo:

 for line in fh: pass last = line 

No es necesario molestarse con el indicador binario que podría usar open(fname) .

ETA : Ya que tiene muchos archivos para trabajar, puede crear una muestra de un par de docenas de archivos utilizando random.sample y ejecutar este código en ellos para determinar la longitud de la última línea. Con un gran valor a priori del cambio de posición (digamos 1 MB). Esto le ayudará a estimar el valor para la ejecución completa.

Puede abrir el archivo para leer y leer la primera línea utilizando la línea de lectura incorporada readline() , luego buscar el final del archivo y retroceder hasta encontrar la EOL anterior de la línea y leer la última línea desde allí.

 with open(file, "rb") as f: first = f.readline() # Read the first line. f.seek(-2, os.SEEK_END) # Jump to the second last byte. while f.read(1) != b"\n": # Until EOL is found... f.seek(-2, os.SEEK_CUR) # ...jump back the read byte plus one more. last = f.readline() # Read last line. 

Saltar al segundo último byte en lugar del último impide que regrese directamente debido a un EOL final. Mientras retrocede, también querrá pasar dos bytes, ya que la lectura y verificación de EOL empuja la posición un paso hacia adelante.

Cuando se utiliza, seek el formato es fseek(offset, whence=0) donde whence significa a qué se fseek(offset, whence=0) el offset. Cita de docs.python.org :

  • SEEK_SET o 0 = buscar desde el inicio de la transmisión (el valor predeterminado); el desplazamiento debe ser un número devuelto por TextIOBase.tell () o cero. Cualquier otro valor de compensación produce un comportamiento indefinido.
  • SEEK_CUR o 1 = “buscar” a la posición actual; el desplazamiento debe ser cero, que no es una operación (todos los demás valores no son compatibles).
  • SEEK_END o 2 = buscar hasta el final de la secuencia; el desplazamiento debe ser cero (todos los demás valores no son compatibles).

Ejecutarlo 10 veces en un archivo con 6k líneas que sumn un total de 200kB me dio 1.62s frente a 6.92s al compararlo con el bucle for que se sugirió anteriormente. Usando un archivo de 1.3GB, aún con 6k líneas, cien veces resultó en 8.93 vs 86.95.

 with open(file, "rb") as f: first = f.readline() # Read the first line. for last in f: pass # Loop through the whole file reading it all. 

Aquí hay una versión modificada de la respuesta de SilentGhost que hará lo que quieras.

 with open(fname, 'rb') as fh: first = next(fh) offs = -100 while True: fh.seek(offs, 2) lines = fh.readlines() if len(lines)>1: last = lines[-1] break offs *= 2 print first print last 

No hay necesidad de un límite superior para la longitud de la línea aquí.

¿Puedes usar los comandos de Unix? Creo que usar head -1 y tail -n 1 son probablemente los métodos más eficientes. Alternativamente, puede usar un fid.readline() simple para obtener la primera línea y fid.readlines()[-1] , pero eso puede requerir demasiada memoria.

Esta es mi solución, compatible también con Python3. También gestiona casos fronterizos, pero pierde la compatibilidad con utf-16:

 def tail(filepath): """ @author Marco Sulla (marcosullaroma@gmail.com) @date May 31, 2016 """ try: filepath.is_file fp = str(filepath) except AttributeError: fp = filepath with open(fp, "rb") as f: size = os.stat(fp).st_size start_pos = 0 if size - 1 < 0 else size - 1 if start_pos != 0: f.seek(start_pos) char = f.read(1) if char == b"\n": start_pos -= 1 f.seek(start_pos) if start_pos == 0: f.seek(start_pos) else: char = "" for pos in range(start_pos, -1, -1): f.seek(pos) char = f.read(1) if char == b"\n": break return f.readline() 

Está inspirado en la respuesta de Trasp y el comentario de AnotherParker .

Primero abra el archivo en modo de lectura. Luego use el método readlines () para leer línea por línea. Todas las líneas almacenadas en una lista. Ahora puede usar los segmentos de lista para obtener la primera y la última línea del archivo.

  a=open('file.txt','rb') lines = a.readlines() if lines: first_line = lines[:1] last_line = lines[-1] 
 w=open(file.txt, 'r') print ('first line is : ',w.readline()) for line in w: x= line print ('last line is : ',x) w.close() 

El bucle for ejecuta a través de las líneas y x obtiene la última línea en la iteración final.

 with open("myfile.txt") as f: lines = f.readlines() first_row = lines[0] print first_row last_row = lines[-1] print last_row 

Aquí hay una extensión de la respuesta de @ Trasp que tiene lógica adicional para manejar el caso de la esquina de un archivo que tiene solo una línea. Puede ser útil manejar este caso si desea leer repetidamente la última línea de un archivo que se actualiza continuamente. Sin esto, si intenta capturar la última línea de un archivo que se acaba de crear y tiene solo una línea, IOError: [Errno 22] Invalid argument generará un IOError: [Errno 22] Invalid argument .

 def tail(filepath): with open(filepath, "rb") as f: first = f.readline() # Read the first line. f.seek(-2, 2) # Jump to the second last byte. while f.read(1) != b"\n": # Until EOL is found... try: f.seek(-2, 1) # ...jump back the read byte plus one more. except IOError: f.seek(-1, 1) if f.tell() == 0: break last = f.readline() # Read last line. return last 

Nadie mencionó el uso invertido:

 f=open(file,"r") r=reversed(f.readlines()) last_line_of_file = r.next() 

Conseguir la primera línea es trivialmente fácil. Para la última línea, suponiendo que conozca un límite superior aproximado en la longitud de la línea, os.lseek alguna cantidad de SEEK_END encuentra la segunda a la última línea que termina y luego readline () la última línea.

 with open(filename, "r") as f: first = f.readline() if f.read(1) == '': return first f.seek(-2, 2) # Jump to the second last byte. while f.read(1) != b"\n": # Until EOL is found... f.seek(-2, 1) # ...jump back the read byte plus one more. last = f.readline() # Read last line. return last 

La respuesta anterior es una versión modificada de las respuestas anteriores que maneja el caso de que solo hay una línea en el archivo