Как дешево получить счетчик строк в Python?



Мне нужно получить количество строк большого файла (сотни тысяч строк) в python. Какой самый эффективный способ памяти и времени?



на данный момент я делаю:



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


можно ли сделать лучше?

602   30  

30 ответов:

вы не можете получить лучше, чем это.

В конце концов, любое решение должно будет прочитать весь файл, выяснить, сколько \n У вас есть, и вернуть этот результат.

у вас есть лучший способ сделать это, не читая весь файл? Не уверенный... Лучшим решением всегда будет I / O-bound, лучшее, что вы можете сделать, это убедиться, что вы не используете ненужную память, но похоже, что у вас это есть.

одна строка, вероятно, довольно быстро:

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

Я считаю, что файл с отображением памяти будет самым быстрым решением. Я попробовал четыре функции: функция, опубликованная OP (opcount); простая итерация по строкам в файле (simplecount); readline с отображенным в памяти файлом (mmap) (mapcount); и решение для чтения буфера, предложенное Николаем Харечко (bufcount).

я запускал каждую функцию пять раз и рассчитал среднее время выполнения для текстового файла объемом 1,2 миллиона строк.

Windows XP, Python 2.5, 2 ГБ оперативной памяти, 2 ГГц Процессор AMD

вот мои результаты:

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

Edit: номера для Python 2.6:

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

таким образом, стратегия чтения буфера кажется самой быстрой для Windows / Python 2.6

вот код:

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

мне пришлось опубликовать это на аналогичный вопрос, пока мой рейтинг репутации не подскочил немного (спасибо тому, кто меня ударил!).

все эти решения игнорируют один из способов сделать этот запуск значительно быстрее, а именно с помощью интерфейса unbuffered (raw), используя bytearrays и выполняя собственную буферизацию. (Это применимо только в Python 3. В Python 2 необработанный интерфейс может использоваться или не использоваться по умолчанию, но в Python 3 Вы будете по умолчанию использовать Unicode.)

С помощью модифицированного версия инструмента синхронизации, я считаю, что следующий код быстрее (и немного более pythonic), чем любое из предлагаемых решений:

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

через отдельную функцию генератора, это работает немного быстрее:

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 )

Это можно сделать полностью с помощью генераторов выражений в строке с помощью itertools, но это выглядит довольно странно:

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 )

вот мои тайминги:

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

вы можете выполнить подпроцесс и запустить 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])

вот программа python для использования многопроцессорной библиотеки для распределения подсчета строк между машинами / ядрами. Мой тест улучшает подсчет 20-миллионного файла строки от 26 секунд до 7 секунд с помощью 8-ядерный сервер windows 64. Примечание: не используя отображение памяти делает вещи намного медленнее.

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

Я бы использовал метод объекта файла Python readlines следующим образом:

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

это открывает файл, создает список строк в файле, подсчитывает длину списка, сохраняет его в переменную и снова закрывает файл.

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

Я получил небольшое (4-8%) улучшение с этой версией, которая повторно использует постоянный буфер, поэтому он должен избегать любой памяти или накладных расходов GC:

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

вы можете поиграть с размером буфера и, возможно, увидеть небольшое улучшение.

Кайла!--5-->

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

вероятно, лучше, Альтернатива для этого

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

вот сравнение производительности обоих

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

одно из решений строку

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

мой фрагмент

ОС.система ('wc-l *.txt')

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

этот код короче и яснее. Это, наверное, лучший способ:

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

Это самая быстрая вещь, которую я нашел, используя чистый python. Вы можете использовать любой объем памяти путем установки буфер, хотя 2**16, кажется, сладкое пятно на моем компьютере.

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), ''))

Я нашел ответ здесь почему чтение строк из stdin намного медленнее в C++ , чем в Python? и подправил его совсем чуть-чуть. Его очень хорошо читать, чтобы понять, как быстро считать строки, хотя wc -l по-прежнему около 75% быстрее, чем все остальное.

просто для завершения вышеуказанных методов я попробовал вариант с модулем fileinput:

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

и передал файл строк 60mil всем вышеуказанным методам:

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

меня немного удивляет, что fileinput настолько плох и масштабируется намного хуже, чем все другие методы...

вот что я использую, кажется довольно чистым:

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

обновление: это немного быстрее, чем использование чистого python, но за счет использования памяти. Подпроцесс будет разветвлять новый процесс с тем же объемом памяти, что и родительский процесс, пока он выполняет вашу команду.

результатом открытия файла является итератор, который может быть преобразован в последовательность, имеющую длину:

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

это более лаконично, чем ваш явный цикл, и позволяет избежать enumerate.

Как по мне этот вариант будет самым быстрым:

#!/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()

причины: буферизация быстрее, чем чтение строки за строкой и string.count также очень быстро

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

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

Я изменил случай буфера следующим образом:

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

теперь также учитываются пустые файлы и последняя строка (без \n).

однострочное решение bash, подобное ответ, используя современные subprocess.check_output функция:

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

Как насчет этого

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

Как насчет этого?

import fileinput
import sys

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

fileinput.close()
print counter

Как насчет этого-вкладыш:

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

занимает 0.003 сек с помощью этого метода, чтобы время его на 3900 строки файла

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

Если кто-то хочет получить счетчик строк дешево в Python в Linux, я рекомендую этот метод:

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

file_path может быть как абстрактным путем к файлу, так и относительным путем. Надеюсь, это может помочь.

другая возможность:

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

Почему бы не прочитать первые 100 и последние 100 строк и оценить среднюю длину строки, а затем разделить общий размер файла на эти числа? Если вам не нужно точное значение, это может сработать.

так же:

lines = 0
with open(path) as f:
    for line in f:
        lines += 1

Comments

    Ничего не найдено.