¿Cómo obtener una cuenta de línea barata en Python?

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 de memoria y tiempo?

En el momento que hago:

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

¿Es posible hacerlo mejor?

Related of "¿Cómo obtener una cuenta de línea barata en Python?"

No puedes ser mejor que eso.

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

¿Tienes una mejor manera de hacerlo sin leer todo el archivo? No estoy seguro … La mejor solución siempre estará vinculada a la E / S, lo mejor que puede hacer es asegurarse de no usar memoria innecesaria, pero parece que tiene todo eso cubierto.

Una línea, probablemente bastante rápida:

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

Creo que un archivo mapeado en memoria será la solución más rápida. opcount cuatro funciones: la función publicada por el OP ( opcount ); una iteración simple sobre las líneas en el archivo ( simplecount ); readline con un archivo mapeado en memoria (mmap) ( mapcount ); y la solución de lectura de búfer 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 

Edición : números para Python 2.6:

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

Así que la estrategia de lectura de 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): f = open(filename, "r+") 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)) 

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

Todas estas soluciones ignoran una forma de hacer que esto funcione considerablemente más rápido, es decir, mediante el uso de la interfaz sin búfer (en bruto), el uso de bytearrays y la creación de su propio búfer. (Esto solo se aplica en Python 3. En Python 2, la interfaz sin formato puede o no usarse de forma predeterminada, pero en Python 3, se establecerá un valor predeterminado en Unicode).

Al usar una versión modificada de la herramienta de tiempo, creo que el siguiente código es más rápido (y ligeramente más python) 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 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 expresiones de generadores en línea usando itertools, pero se ve bastante raro:

 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 

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

Aquí hay un progtwig de Python para usar la biblioteca de multiprocesamiento para distribuir el conteo de líneas a través de máquinas / núcleos. Mi prueba mejora el conteo de un archivo de 20 millones de líneas de 26 segundos a 7 segundos utilizando un servidor Windows 64 de 8 núcleos. Nota: no usar la asignación de memoria hace las cosas 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 ) ) 

Yo usaría readlines método de objeto de archivo de Python, como sigue:

 with open(input_file) as foo: lines = len(foo.readlines()) 

Esto abre el archivo, crea una lista de líneas en el archivo, cuenta la longitud de la lista, la guarda en una variable y vuelve a cerrar el archivo.

Esto es lo que uso, parece bastante limpio:

 import subprocess def count_file_lines(file_path): """ Counts the number of lines in a file using wc utility. :param file_path: path to file :return: int, no of lines """ num = subprocess.check_output(['wc', '-l', file_path]) num = num.split(' ') return int(num[0]) 

ACTUALIZACIÓN: Esto es un poco más rápido que usar python puro, pero a costa del uso de la memoria. Subproceso procesará un nuevo proceso con la misma huella de memoria que el proceso principal mientras ejecuta su comando.

 def file_len(full_path): """ Count number of lines in a file.""" f = open(full_path) nr_of_lines = sum(1 for line in f) f.close() return nr_of_lines 

Obtuve una pequeña mejora (4-8%) con esta versión que reutiliza un búfer constante, por lo que debería evitar cualquier sobrecarga de memoria o GC:

 lines = 0 buffer = bytearray(2048) with open(filename) as f: while f.readinto(buffer) > 0: lines += buffer.count('\n') 

Puedes jugar con el tamaño del búfer y tal vez ver una pequeña mejora.

La respuesta de kyle

 num_lines = sum(1 for line in open('my_file.txt')) 

Es probablemente lo mejor, una alternativa para esto es

 num_lines = len(open('my_file.txt').read().splitlines()) 

Aquí está la comparación de rendimiento de ambos

 In [20]: timeit sum(1 for line in open('Charts.ipynb')) 100000 loops, best of 3: 9.79 µs per loop In [21]: timeit len(open('Charts.ipynb').read().splitlines()) 100000 loops, best of 3: 12 µs per loop 

una solución de línea

 import os os.system("wc -l filename") 

mi fragmento

os.system (‘wc -l * .txt’)

 0 bar.txt 1000 command.txt 3 test_file.txt 1003 total 

Una solución de bash de una línea similar a esta respuesta , utilizando la función moderna subprocess.check_output :

 def line_count(file): return int(subprocess.check_output('wc -l {}'.format(file), shell=True).split()[0]) 

Esta es la cosa más rápida que he encontrado usando python puro. Puede usar la cantidad de memoria que desee configurando el búfer, aunque 2 ** 16 parece ser un punto dulce en mi computadora.

 from functools import partial buffer=2**16 with open(myfile) as f: print sum(x.count('\n') for x in iter(partial(f.read,buffer), '')) 

Encontré la respuesta aquí ¿Por qué leer líneas de stdin es mucho más lento en C ++ que en Python? y lo pellizcó un poquito. Es una muy buena lectura para entender cómo contar líneas rápidamente, aunque wc -l es aproximadamente un 75% más rápido que cualquier otra cosa.

Solo para completar los métodos anteriores, probé una variante con el módulo fileinput:

 import fileinput as fi def filecount(fname): for line in fi.input(fname): pass return fi.lineno() 

Y pasó un archivo de 60mil líneas a todos los métodos mencionados anteriormente:

 mapcount : 6.1331050396 simplecount : 4.588793993 opcount : 4.42918205261 filecount : 43.2780818939 bufcount : 0.170812129974 

Es una pequeña sorpresa para mí que la entrada de archivos sea tan mala y se escale mucho peor que todos los otros métodos …

Este código es más corto y claro. Probablemente sea la mejor manera:

 num_lines = open('yourfile.ext').read().count('\n') 

En cuanto a mí esta variante será la más rápida:

 #!/usr/bin/env python def main(): 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) print lines if __name__ == '__main__': main() 

razones: el almacenamiento en búfer más rápido que leer línea por línea y string.count también es muy rápido

 print open('file.txt', 'r').read().count("\n") + 1 

Método simple:

num_lines = len(list(open('myfile.txt')))

El resultado de abrir un archivo es un iterador, que se puede convertir a una secuencia, que tiene una longitud:

 with open(filename) as f: return len(list(f)) 

esto es más conciso que su bucle explícito, y evita la enumerate .

He modificado el caso de búfer de esta manera:

 def CountLines(filename): f = open(filename) try: lines = 1 buf_size = 1024 * 1024 read_f = f.read # loop optimization buf = read_f(buf_size) # Empty file if not buf: return 0 while buf: lines += buf.count('\n') buf = read_f(buf_size) return lines finally: f.close() 

Ahora también se cuentan los archivos vacíos y la última línea (sin \ n).

Que hay de esto

 def file_len(fname): counts = itertools.count() with open(fname) as f: for _ in f: counts.next() return counts.next() 

count = max(enumerate(open(filename)))[0]

Si uno quiere obtener el recuento de líneas a bajo precio en Python en Linux, recomiendo este método:

 import os print os.popen("wc -l file_path").readline().split()[0] 

file_path puede ser tanto una ruta de archivo abstracta como una ruta relativa. Espero que esto pueda ayudar.

¿Qué tal esto?

 import fileinput import sys counter=0 for line in fileinput.input([sys.argv[1]]): counter+=1 fileinput.close() print counter 

¿Qué tal este one-liner:

 file_length = len(open('myfile.txt','r').read().split('\n')) 

Toma 0.003 segundos usando este método para cronometrarlo en un archivo de 3900 líneas

 def c(): import time s = time.time() file_length = len(open('myfile.txt','r').read().split('\n')) print time.time() - s 
 def line_count(path): count = 0 with open(path) as lines: for count, l in enumerate(lines, start=1): pass return count 

Puedes usar el módulo os.path de la siguiente manera:

 import os import subprocess Number_lines = int( (subprocess.Popen( 'wc -l {0}'.format( Filename ), shell=True, stdout=subprocess.PIPE).stdout).readlines()[0].split()[0] ) 

, donde Filename es la ruta absoluta del archivo.

Otra posibilidad:

 import subprocess def num_lines_in_file(fpath): return int(subprocess.check_output('wc -l %s' % fpath, shell=True).strip().split()[0]) 
 def count_text_file_lines(path): with open(path, 'rt') as file: line_count = sum(1 for _line in file) return line_count