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

Resuelto SilentGhost asked hace 15 años • 44 respuestas

¿Cómo puedo obtener un recuento de líneas de un archivo grande de la manera más eficiente en cuanto a memoria y tiempo?

def file_len(filename):
    with open(filename) as f:
        for i, _ in enumerate(f):
            pass
    return i + 1
SilentGhost avatar May 10 '09 17:05 SilentGhost
Aceptado

Una línea, más rápida que el forbucle del OP (aunque no la más rápida) y muy concisa:

num_lines = sum(1 for _ in open('myfile.txt'))

También puedes aumentar la velocidad (y la robustez) usando rbUel modo e incluirlo en un withbloque para cerrar el archivo:

with open("myfile.txt", "rbU") as f:
    num_lines = sum(1 for _ in f)

Nota : El modo Uin rbUestá obsoleto desde Python 3.3 y versiones posteriores, por lo que deberíamos usarlo rben lugar de rbU(y se eliminó en Python 3.11 ).

Kyle avatar Jun 19 '2009 19:06 Kyle

No hay nada mejor que eso.

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

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

[Editar mayo de 2023]

Como se comentó en muchas otras respuestas, en Python 3 existen mejores alternativas. El forbucle no es el más eficiente. Por ejemplo, usar mmapbuffers o es más eficiente.

Yuval Adam avatar May 10 '2009 10:05 Yuval Adam

Creo que un archivo mapeado en memoria será la solución más rápida. Probé cuatro funciones: la función publicada por el OP ( opcount); una iteración simple sobre las líneas del archivo ( simplecount); readline con un archivo mapeado en memoria (mmap) ( mapcount); y la solución de lectura de buffer ofrecida por Mykola Kharechko ( bufcount).

Ejecuté cada función cinco veces y calculé el tiempo de ejecución promedio para un archivo de texto de 1,2 millones de líneas.

Windows XP , Python 2.5, 2 GB de RAM, procesador AMD de 2 GHz

Aquí están mis resultados:

mapcount : 0.465599966049
simplecount : 0.756399965286
bufcount : 0.546800041199
opcount : 0.718600034714

Números para Python 2.6:

mapcount : 0.471799945831
simplecount : 0.634400033951
bufcount : 0.468800067902
opcount : 0.602999973297

Entonces, la estrategia de lectura del búfer parece ser la más rápida para Windows/Python 2.6

Aquí está el código:

from __future__ import with_statement
import time
import mmap
import random
from collections import defaultdict

def mapcount(filename):
    with open(filename, "r+") as f:
        buf = mmap.mmap(f.fileno(), 0)
        lines = 0
        readline = buf.readline
        while readline():
            lines += 1
        return lines

def simplecount(filename):
    lines = 0
    for line in open(filename):
        lines += 1
    return lines

def bufcount(filename):
    f = open(filename)
    lines = 0
    buf_size = 1024 * 1024
    read_f = f.read # loop optimization

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

    return lines

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


counts = defaultdict(list)

for i in range(5):
    for func in [mapcount, simplecount, bufcount, opcount]:
        start_time = time.time()
        assert func("big_file.txt") == 1209138
        counts[func].append(time.time() - start_time)

for key, vals in counts.items():
    print key.__name__, ":", sum(vals) / float(len(vals))
Ryan Ginstrom avatar May 12 '2009 02:05 Ryan Ginstrom

Todas estas soluciones ignoran una forma de hacer que esto se ejecute considerablemente más rápido, es decir, utilizando la interfaz sin búfer (sin búfer), usando bytearrays y haciendo su propio almacenamiento en búfer. (Esto solo se aplica en Python 3. En Python 2, la interfaz sin formato puede usarse o no de forma predeterminada, pero en Python 3, se utilizará Unicode de forma 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 Pythonic) 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
Michael Bacon avatar Dec 17 '2014 04:12 Michael Bacon

Podrías ejecutar un subproceso y ejecutarwc -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])
Ólafur Waage avatar May 10 '2009 10:05 Ólafur Waage