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

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

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

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

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

Ответ 1

Вы не можете добиться лучшего.

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

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

Ответ 2

Одна строка, возможно довольно быстро:

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

Ответ 3

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

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

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

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

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

Изменить: номера для 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))

Ответ 4

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

Все эти решения игнорируют один из способов сделать этот запуск значительно быстрее, а именно, используя небуферизованный (необработанный) интерфейс, используя 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

Используя отдельную функцию генератора, это быстрее запускает smidge:

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 )

Это можно сделать полностью с выражениями генераторов in-line, использующими 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

Ответ 5

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

Ответ 6

Вот программа 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 ) )

Ответ 7

Я бы использовал метод файлового объекта Python readlines, как показано ниже:

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

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

Ответ 8

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

Ответ 9

Одноканальное решение:

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

Мой фрагмент:

>>> os.system('wc -l *.txt')

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

Ответ 10

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

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, но за счет использования памяти. Подпроцесс запустит новый процесс с тем же объемом памяти, что и родительский процесс, пока он выполняет вашу команду.

Ответ 11

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

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

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

Ответ 12

Ответ Кайла

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

Ответ 13

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

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

Ответ 14

Это самая быстрая вещь, которую я нашел, используя чистый 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 намного медленнее на С++, чем Python? и немного изменили его. Его очень хорошо читать, чтобы понять, как быстро рассчитывать строки, хотя wc -l все еще примерно на 75% быстрее, чем что-либо еще.

Ответ 15

Чтобы завершить описанные выше методы, я попробовал вариант с файловым модулем:

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

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

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

Немного сюрприз для меня, что fileinput - это то, что плохо и масштабируется намного хуже, чем все другие методы...

Ответ 16

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

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

Ответ 17

Для меня этот вариант будет самым быстрым:

#!/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 также очень быстро

Ответ 18

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

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) подсчитываются.

Ответ 19

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

Ответ 20

Простой метод:

1)

>>> f = len(open("myfile.txt").readlines())
>>> f

430

2)

>>> f = open("myfile.txt").read().count('\n')
>>> f
430
>>>

3)

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

Ответ 21

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

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

Ответ 22

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

Ответ 23

def line_count(path):
    count = 0
    with open(path) as lines:
        for count, l in enumerate(lines, start=1):
            pass
    return count

Ответ 24

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

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

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

Ответ 25

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

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

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

Ответ 26

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

import fileinput
import sys

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

fileinput.close()
print counter

Ответ 27

Как насчет этого однострочного интерфейса:

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

Ответ 28

Вы можете использовать модуль os.path следующим образом:

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

где Filename - это абсолютный путь к файлу.

Ответ 29

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

import subprocess

def num_lines_in_file(fpath):
    return int(subprocess.check_output('wc -l %s' % fpath, shell=True).strip().split()[0])

Ответ 30

def count_text_file_lines(path):
    with open(path, 'rt') as file:
        line_count = sum(1 for _line in file)
    return line_count