¿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 objetivo es encontrar el rango de tiempo. Todos los tiempos están en orden por lo que la primera línea será la primera vez y la última línea será la última vez. Sólo 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 son relativamente grandes en longitud, alrededor de 1-2 millones de líneas cada uno y tengo que hacer esto para varios cientos de archivos.

Author: mik01aj, 2010-07-27

12 answers

Docs for io module

with open(fname, 'rb') as fh:
    first = next(fh).decode()

    fh.seek(-1024, 2)
    last = fh.readlines()[-1].decode()

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

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

for line in fh:
    pass
last = line

No necesita molestarse con la bandera binaria que podría usar open(fname).

ETA : Dado que tiene muchos archivos en los que 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 valor a priori grande del cambio de posición (digamos 1 MB). Esto le ayudará a estimar el valor para la ejecución completa.

 51
Author: SilentGhost,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-06 04:27:14

Puede abrir el archivo para leer y leer la primera línea usando el builtin readline(), luego busque el final del archivo y retroceda hasta encontrar la línea anterior EOL y lea 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 penúltimo byte en lugar del último evita que regrese directamente debido a un EOL final. Mientras que usted está dando un paso atrás también querrá paso dos bytes ya que la lectura y comprobación de EOL empuja el posición hacia adelante un paso.

Cuando se utiliza seek el formato es fseek(offset, whence=0) donde whence significa que lo que el desplazamiento es relativo. Cita de docs.python.org:

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

Ejecutarlo a través de timeit 10k veces en un archivo con líneas 6k que suman 200kB me dio 1.62 s vs 6.92 s cuando se compara con el bucle for debajo del que se sugirió antes. Usando un archivo de tamaño 1.3 GB, aún con líneas de 6k, 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.
 57
Author: Trasp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-13 09:35:17

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í.

 22
Author: mik01aj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-27 18:45:10

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

 8
Author: beitar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-27 18:07:27

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

def tail(filepath):
    """
    @author Marco Sulla ([email protected])
    @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()

Es emitido por La respuesta de Traspas y el comentario de otro interlocutor.

 4
Author: Marco Sulla,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:09:59

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 list slices 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]
 3
Author: Srinivasreddy Jakkireddy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-22 08:25:10
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 corre a través de las líneas y x obtiene la última línea en la iteración final.

 3
Author: VipeR,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-10 20:27:15
with open("myfile.txt") as f:
    lines = f.readlines()
    first_row = lines[0]
    print first_row
    last_row = lines[-1]
    print last_row
 1
Author: Riccardo Volpe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-31 01:40:50

Aquí hay una extensión de la respuesta de @Traspas que tiene lógica adicional para manejar el caso de 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 agarrar la última línea de un archivo que acaba de ser creado y tiene solo una línea, IOError: [Errno 22] Invalid argument se levantará.

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
 1
Author: tony_tiger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-05 17:48:56

Nadie mencionó usar reversed:

f=open(file,"r")
r=reversed(f.readlines())
last_line_of_file = r.next()
 1
Author: Michael Meanswell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-20 05:17:30

Obtener la primera línea es trivialmente fácil. Para la última línea, suponiendo que conoce un límite superior aproximado en la longitud de la línea, os.lseek alguna cantidad de SEEK_END encuentra el final de la penúltima línea y luego readline() la última línea.

 0
Author: msw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-27 18:08:31
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 haya una línea en el archivo

 0
Author: user37940,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-29 08:50:56