Ну, память может исчерпать себя в любое время, асинхронно и непредсказуемо - вот почему обещание «никогда не исключение» на самом деле не применимо там (как, скажем, в Java, где каждыйМетод должен указывать, какие исключения он может вызывать, некоторые исключения исключаются из этого правила, поскольку практически любой метод может непредсказуемо вызвать их в любое время из-за нехватки ресурсов или других системных проблем).
linecache
пытается прочитать весь файл.Ваша единственная простая альтернатива (надеюсь, вы не спешите) - читать по одной строке за раз с самого начала ...:
def readoneline(filepath, linenum):
if linenum < 0: return ''
with open(filepath) as f:
for i, line in enumerate(filepath):
if i == linenum: return line
return ''
Здесь linenum
равно 0 (если выэто не нравится, и ваш Python равен 2.6 или лучше, передайте начальное значение от 1
до enumerate
), а возвращаемое значение - пустая строка для недопустимых номеров строк.
Несколько быстрее (и лот более сложный) - это, скажем, 100 МБ за раз (в двоичном режиме) в буфер;подсчитать количество концов строк в буфере (просто вызов .count('\n')
для строкового объекта буфера);как только итоговое итоговое значение конца строки превысит искомое количество, найдите N-ный конец строки, находящийся в данный момент в буфере (где N
- это разница между linenum
, здесь на основе 1 и предыдущим промежуточным итогомконец строки), прочитайте немного больше, если N+1
конец строки также не находится в буфере (поскольку это точка, где заканчивается ваша строка), извлеките соответствующую подстроку.Не просто пара строк за with
и возвращается для аномальных случаев ...; -).
Редактировать : так как OP комментирует сомнение в том, что чтение буфером вместопострочно может иметь значение для производительности, я удалил старый фрагмент кода, где я измерял два подхода для несколько связанных задач - подсчет количества строк с буферным подходом, цикл по строкам или чтение всегофайл в памяти одним глотком (readlines
).Целевой файл - kjv.txt
, стандартный английский текст Библии короля Иакова, одна строка в стихе, ASCII:
$ wc kjv.txt
114150 821108 4834378 kjv.txt
Платформа представляет собой ноутбук MacOS Pro, OSX 10.5.8,Intel Core 2 Duo на частоте 2,4 ГГц, Python 2.6.5.
Модуль для теста, readkjv.py
:
def byline(fn='kjv.txt'):
with open(fn) as f:
for i, _ in enumerate(f):
pass
return i +1
def byall(fn='kjv.txt'):
with open(fn) as f:
return len(f.readlines())
def bybuf(fn='kjv.txt', BS=100*1024):
with open(fn, 'rb') as f:
tot = 0
while True:
blk = f.read(BS)
if not blk: return tot
tot += blk.count('\n')
if __name__ == '__main__':
print bybuf()
print byline()
print byall()
print
s просто для подтверждения правильности курса.1040 *
Числа вполне повторяемы.Как вы видите, даже на таком крошечном файле (менее 5 МБ!) Построчные подходы медленнее, чем на основе буфера - просто слишком много напрасных усилий!
Чтобы проверить масштабируемость, я затем использовалфайл в 4 раза больше, как показано ниже:
$ cat kjv.txt kjv.txt kjv.txt kjv.txt >k4.txt
$ wc k4.txt
456600 3284432 19337512 k4.txt
$ py26 -mtimeit -s'import readkjv' 'readkjv.bybuf()'
10 loops, best of 3: 25.4 msec per loop
$ py26 -mtimeit -s'import readkjv' 'readkjv.bybuf("k4.txt")'
10 loops, best of 3: 102 msec per loop
и, как и предполагалось, подход с использованием буфера масштабируется почти линейно.Экстраполяция (всегда рискованное занятие, конечно ;-), чуть менее 200 МБ в секунду кажется предсказуемой производительностью - назовите ее 6 секунд на ГБ, или, может быть, 10 минут на 100 ГБ.
Конечно, эта небольшая программа выполняет только подсчет строк, но (как только ввода-вывода достаточно, чтобы амортизировать постоянные накладные расходы ;-) программа для чтения конкретной строки должна иметь аналогичную производительность (даже если для ее обнаружения требуется больше обработки)«интересующий буфер», это примерно константа объема обработки для буфера заданного размера - предположительно повторяемое деление пополам буфера, чтобы идентифицировать его достаточно малую часть, затем немногоусилие линейное по размеру «буферного остатка», кратно разделенного пополам).
Elegant?Не совсем ... но для скорости довольно трудно победить! -)