¿Cómo obtener el recuento de líneas de un archivo grande de forma económica en Python?

9 minutos de lectura

¿Como obtener el recuento de lineas de un archivo grande
Fantasma silencioso

Necesito obtener un recuento de líneas de un archivo grande (cientos de miles de líneas) en python. ¿Cuál es la forma más eficiente tanto en términos de memoria como de tiempo?

De momento hago:

def file_len(fname):
    with open(fname) as f:
        for i, l in enumerate(f):
            pass
    return i + 1

¿es posible hacerlo mejor?

  • ¿Necesita un recuento de líneas exacto o será suficiente una aproximación?

    – pico

    11 de mayo de 2009 a las 20:14

  • Agregaría i=-1 antes del ciclo for, ya que este código no funciona para archivos vacíos.

    – Maciek Sawicki

    27 de diciembre de 2011 a las 16:13

  • @Legend: apuesto a que pico está pensando, obtenga el tamaño del archivo (con seek (0,2) o equivalente), divídalo por la longitud aproximada de la línea. Podrías leer algunas líneas al principio para adivinar la longitud promedio de las líneas.

    – Ana

    7 febrero 2012 a las 17:02

  • enumerate(f, 1) y deshacerse de la i + 1?

    –Ian Mackinnon

    21 de febrero de 2013 a las 12:25

  • @IanMackinnon funciona para archivos vacíos, pero debe inicializar I para 0 antes del bucle for.

    – scai

    13 de agosto de 2013 a las 16:29

¿Como obtener el recuento de lineas de un archivo grande
adán yuval

No puedes conseguir nada mejor que eso.

Después de todo, cualquier solución tendrá que leer el archivo completo, averiguar cuántos \n tienes, y devolver ese resultado.

¿Tiene una mejor manera de hacerlo sin leer todo el archivo? No estoy seguro… La mejor solución siempre será la E/S, lo mejor que puedes hacer es asegurarte de no usar memoria innecesaria, pero parece que lo tienes cubierto.

  • Exactamente, incluso WC está leyendo el archivo, pero en C y probablemente esté bastante optimizado.

    – Ólafur Waage

    10 de mayo de 2009 a las 10:38

  • Según tengo entendido, el archivo IO de Python también se realiza a través de C. docs.python.org/library/stdtypes.html#file-objects

    – Tomalak

    10 de mayo de 2009 a las 10:41

  • @Tomalak Esa es una pista falsa. Si bien python y wc pueden estar emitiendo las mismas llamadas al sistema, python tiene una sobrecarga de despacho de código de operación que wc no tiene.

    – bobpoekert

    11 de enero de 2013 a las 22:53

  • Puede aproximar un recuento de líneas mediante muestreo. Puede ser miles de veces más rápido. Ver: documentroot.com/2011/02/…

    – Erik Aronesty

    14/06/2016 a las 20:30

  • Otras respuestas parecen indicar que esta respuesta categórica es incorrecta y, por lo tanto, debe eliminarse en lugar de mantenerse como aceptada.

    – Skippy el Gran Gourou

    25 de enero de 2017 a las 13:59


  • Parece que wccount() es el mas rapido gist.github.com/0ac760859e614cd03652

    – jfs

    31 de enero de 2011 a las 8:18

  • La lectura almacenada en búfer es la solución más rápida, no mmap o wccount. Consulte stackoverflow.com/a/68385697/353337.

    – Nico Schlömer

    14 de julio de 2021 a las 22:23

1646964973 985 ¿Como obtener el recuento de lineas de un archivo grande
miguel tocino

Tuve que publicar esto en una pregunta similar hasta que mi puntaje de reputación saltó un poco (¡gracias a quien me golpeó!).

Todas estas soluciones ignoran una forma de hacer que esto se ejecute considerablemente más rápido, a saber, mediante el uso de la interfaz sin búfer (sin procesar), el uso de bytearrays y haciendo su propio almacenamiento en búfer. (Esto solo se aplica en Python 3. En Python 2, la interfaz sin procesar puede usarse o no de manera predeterminada, pero en Python 3, usará Unicode de manera predeterminada).

Usando una versión modificada de la herramienta de sincronización, creo que el siguiente código es más rápido (y marginalmente más pitónico) que cualquiera de las soluciones ofrecidas:

def rawcount(filename):
    f = open(filename, 'rb')
    lines = 0
    buf_size = 1024 * 1024
    read_f = f.raw.read

    buf = read_f(buf_size)
    while buf:
        lines += buf.count(b'\n')
        buf = read_f(buf_size)

    return lines

Usando una función de generador separada, esto se ejecuta un poco más rápido:

def _make_gen(reader):
    b = reader(1024 * 1024)
    while b:
        yield b
        b = reader(1024*1024)

def rawgencount(filename):
    f = open(filename, 'rb')
    f_gen = _make_gen(f.raw.read)
    return sum( buf.count(b'\n') for buf in f_gen )

Esto se puede hacer completamente con generadores de expresiones en línea usando itertools, pero se ve bastante extraño:

from itertools import (takewhile,repeat)

def rawincount(filename):
    f = open(filename, 'rb')
    bufgen = takewhile(lambda x: x, (f.raw.read(1024*1024) for _ in repeat(None)))
    return sum( buf.count(b'\n') for buf in bufgen )

Aquí están mis horarios:

function      average, s  min, s   ratio
rawincount        0.0043  0.0041   1.00
rawgencount       0.0044  0.0042   1.01
rawcount          0.0048  0.0045   1.09
bufcount          0.008   0.0068   1.64
wccount           0.01    0.0097   2.35
itercount         0.014   0.014    3.41
opcount           0.02    0.02     4.83
kylecount         0.021   0.021    5.05
simplecount       0.022   0.022    5.25
mapcount          0.037   0.031    7.46

  • Estoy trabajando con archivos de más de 100 Gb, y su rawgencounts es la única solución factible que he visto hasta ahora. ¡Gracias!

    – soungalo

    10 de noviembre de 2015 a las 11:47

  • es wccount en esta tabla para el shell del subproceso wc ¿herramienta?

    – Anentrópico

    11/11/2015 a las 18:05

  • Gracias @michael-bacon, es una muy buena solución. Puedes hacer el rawincount solución de aspecto menos raro usando bufgen = iter(partial(f.raw.read, 1024*1024), b'') en lugar de combinar takewhile y repeat.

    – Pedro H.

    6 de agosto de 2019 a las 6:32

  • Oh, función parcial, sí, ese es un pequeño ajuste agradable. Además, asumí que el intérprete fusionaría el 1024 * 1024 y lo trataría como una constante, pero eso fue una corazonada, no una documentación.

    –Michael Bacon

    8 ago. 2019 a las 16:20


  • @MichaelBacon, ¿sería más rápido abrir el archivo con buffering=0 y luego llamar a leer en lugar de simplemente abrir el archivo como “rb” y llamar a raw.read, ¿o se optimizará para lo mismo?

    – Avraham

    19 de noviembre de 2019 a las 18:53

¿Como obtener el recuento de lineas de un archivo grande
nosklo

Podría ejecutar un subproceso y ejecutar wc -l filename

import subprocess

def file_len(fname):
    p = subprocess.Popen(['wc', '-l', fname], stdout=subprocess.PIPE, 
                                              stderr=subprocess.PIPE)
    result, err = p.communicate()
    if p.returncode != 0:
        raise IOError(err)
    return int(result.strip().split()[0])

  • Estoy trabajando con archivos de más de 100 Gb, y su rawgencounts es la única solución factible que he visto hasta ahora. ¡Gracias!

    – soungalo

    10 de noviembre de 2015 a las 11:47

  • es wccount en esta tabla para el shell del subproceso wc ¿herramienta?

    – Anentrópico

    11/11/2015 a las 18:05

  • Gracias @michael-bacon, es una muy buena solución. Puedes hacer el rawincount solución de aspecto menos raro usando bufgen = iter(partial(f.raw.read, 1024*1024), b'') en lugar de combinar takewhile y repeat.

    – Pedro H.

    6 de agosto de 2019 a las 6:32

  • Oh, función parcial, sí, ese es un pequeño ajuste agradable. Además, asumí que el intérprete fusionaría el 1024 * 1024 y lo trataría como una constante, pero eso fue una corazonada, no una documentación.

    –Michael Bacon

    8 ago. 2019 a las 16:20


  • @MichaelBacon, ¿sería más rápido abrir el archivo con buffering=0 y luego llamar a leer en lugar de simplemente abrir el archivo como “rb” y llamar a raw.read, ¿o se optimizará para lo mismo?

    – Avraham

    19 de noviembre de 2019 a las 18:53

1646964974 144 ¿Como obtener el recuento de lineas de un archivo grande
nombre

Aquí hay un programa de Python para usar la biblioteca de multiprocesamiento para distribuir el conteo de líneas entre máquinas/núcleos. Mi prueba mejora el conteo de un archivo de 20 millones de líneas de 26 segundos a 7 segundos usando un servidor Windows 64 de 8 núcleos. Nota: no usar el mapeo de memoria hace que las cosas sean mucho más lentas.

import multiprocessing, sys, time, os, mmap
import logging, logging.handlers

def init_logger(pid):
    console_format="P{0} %(levelname)s %(message)s".format(pid)
    logger = logging.getLogger()  # New logger at root level
    logger.setLevel( logging.INFO )
    logger.handlers.append( logging.StreamHandler() )
    logger.handlers[0].setFormatter( logging.Formatter( console_format, '%d/%m/%y %H:%M:%S' ) )

def getFileLineCount( queues, pid, processes, file1 ):
    init_logger(pid)
    logging.info( 'start' )

    physical_file = open(file1, "r")
    #  mmap.mmap(fileno, length[, tagname[, access[, offset]]]

    m1 = mmap.mmap( physical_file.fileno(), 0, access=mmap.ACCESS_READ )

    #work out file size to divide up line counting

    fSize = os.stat(file1).st_size
    chunk = (fSize / processes) + 1

    lines = 0

    #get where I start and stop
    _seedStart = chunk * (pid)
    _seekEnd = chunk * (pid+1)
    seekStart = int(_seedStart)
    seekEnd = int(_seekEnd)

    if seekEnd < int(_seekEnd + 1):
        seekEnd += 1

    if _seedStart < int(seekStart + 1):
        seekStart += 1

    if seekEnd > fSize:
        seekEnd = fSize

    #find where to start
    if pid > 0:
        m1.seek( seekStart )
        #read next line
        l1 = m1.readline()  # need to use readline with memory mapped files
        seekStart = m1.tell()

    #tell previous rank my seek start to make their seek end

    if pid > 0:
        queues[pid-1].put( seekStart )
    if pid < processes-1:
        seekEnd = queues[pid].get()

    m1.seek( seekStart )
    l1 = m1.readline()

    while len(l1) > 0:
        lines += 1
        l1 = m1.readline()
        if m1.tell() > seekEnd or len(l1) == 0:
            break

    logging.info( 'done' )
    # add up the results
    if pid == 0:
        for p in range(1,processes):
            lines += queues[0].get()
        queues[0].put(lines) # the total lines counted
    else:
        queues[0].put(lines)

    m1.close()
    physical_file.close()

if __name__ == '__main__':
    init_logger( 'main' )
    if len(sys.argv) > 1:
        file_name = sys.argv[1]
    else:
        logging.fatal( 'parameters required: file-name [processes]' )
        exit()

    t = time.time()
    processes = multiprocessing.cpu_count()
    if len(sys.argv) > 2:
        processes = int(sys.argv[2])
    queues=[] # a queue for each process
    for pid in range(processes):
        queues.append( multiprocessing.Queue() )
    jobs=[]
    prev_pipe = 0
    for pid in range(processes):
        p = multiprocessing.Process( target = getFileLineCount, args=(queues, pid, processes, file_name,) )
        p.start()
        jobs.append(p)

    jobs[0].join() #wait for counting to finish
    lines = queues[0].get()

    logging.info( 'finished {} Lines:{}'.format( time.time() - t, lines ) )

  • ¿Cómo funciona esto con archivos mucho más grandes que la memoria principal? por ejemplo, un archivo de 20 GB en un sistema con 4 GB de RAM y 2 núcleos

    – Brian Minton

    23/09/2014 a las 21:18

  • Difícil de probar ahora, pero supongo que paginaría el archivo dentro y fuera.

    – Martlark

    24/09/2014 a las 11:32

  • Este es un código bastante limpio. Me sorprendió descubrir que es más rápido usar múltiples procesadores. Supuse que el IO sería el cuello de botella. En versiones anteriores de Python, la línea 21 necesita int() como chunk = int((fSize/processes)) + 1

    – Karl Henselin

    30 de diciembre de 2014 a las 19:45

  • ¿Carga todo el archivo en la memoria? ¿Qué pasa con un incendio más grande donde el tamaño es más grande que la memoria RAM en la computadora?

    – pelos

    21 dic 2018 a las 21:30

  • ¿Te importaría si formateé la respuesta con negro? negro.vercel.app

    – Martín Tomas

    5 de febrero a las 8:27

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad