Как дешево получить счетчик строк в Python?
Мне нужно получить количество строк большого файла (сотни тысяч строк) в python. Какой самый эффективный способ памяти и времени?
на данный момент я делаю:
def file_len(fname):
with open(fname) as f:
for i, l in enumerate(f):
pass
return i + 1
можно ли сделать лучше?
30 ответов:
вы не можете получить лучше, чем это.
В конце концов, любое решение должно будет прочитать весь файл, выяснить, сколько
\nУ вас есть, и вернуть этот результат.у вас есть лучший способ сделать это, не читая весь файл? Не уверенный... Лучшим решением всегда будет I / O-bound, лучшее, что вы можете сделать, это убедиться, что вы не используете ненужную память, но похоже, что у вас это есть.
Я считаю, что файл с отображением памяти будет самым быстрым решением. Я попробовал четыре функции: функция, опубликованная 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.718600034714Edit: номера для 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 filenameimport 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также очень быстро
Я изменил случай буфера следующим образом:
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 строк и оценить среднюю длину строки, а затем разделить общий размер файла на эти числа? Если вам не нужно точное значение, это может сработать.
Comments