Вопрос-Ответ

How to get the line count of a large file cheaply in Python

Как дешево получить количество строк большого файла в Python

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

def file_len(filename):
with open(filename) as f:
for i, _ in enumerate(f):
pass
return i + 1
Переведено автоматически
Ответ 1

Одна строка, быстрее, чем for цикл OP (хотя и не самый быстрый) и очень лаконичный:

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

Вы также можете повысить скорость (и надежность), используя rbU mode и включив его в with блок для закрытия файла:

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

Примечание: Режим U in rbU устарел начиная с Python 3.3 и выше, поэтому мы должны использовать rb вместо rbU (и он был удален в Python 3.11).

Ответ 2

Лучше этого ничего не придумаешь.

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

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

[Редактировать май 2023]

Как отмечалось во многих других ответах, в Python 3 есть альтернативы получше. for Цикл не самый эффективный. Например, использование mmap буферов or более эффективно.

Ответ 3

Я считаю, что файл с отображением в память будет самым быстрым решением. Я попробовал четыре функции: функцию, опубликованную OP (opcount); простую итерацию по строкам в файле (simplecount); чтение строки с помощью файла, отображенного в памяти (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):
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))
Ответ 4

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

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

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)

Это можно полностью выполнить с помощью встроенных выражений generators с помощью 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
python