Here's an answer like MizardX's, but without its apparent problem of taking quadratic time in the worst case from rescanning the working string repeatedly for newlines as chunks are added.
Compared to the activestate solution (which also seems to be quadratic), this doesn't blow up given an empty file, and does one seek per block read instead of two.
Compared to spawning 'tail', this is self-contained. (But 'tail' is best if you have it.)
Compared to grabbing a few kB off the end and hoping it's enough, this works for any line length.
import os
def reversed_lines(file):
"Generate the lines of file in reverse order."
tail = [] # Tail of the line whose head is not yet read.
for block in reversed_blocks(file):
# A line is a list of strings to avoid quadratic concatenation.
# (And trying to avoid 1-element lists would complicate the code.)
linelists = [[line] for line in block.splitlines()]
linelists[-1].extend(tail)
for linelist in reversed(linelists[1:]):
yield ''.join(linelist)
tail = linelists[0]
if tail: yield ''.join(tail)
def reversed_blocks(file, blocksize=4096):
"Generate blocks of file's contents in reverse order."
file.seek(0, os.SEEK_END)
here = file.tell()
while 0 < here:
delta = min(blocksize, here)
file.seek(here - delta, os.SEEK_SET)
yield file.read(delta)
here -= delta
To use it as requested:
import itertools
import operator
def check_last_10_lines(file, key):
for line in head(reversed_lines(file), 10):
if line == key:
print 'FOUND'
break
def head(iter, n):
"Like foo[:n], but works on iterators."
return itertools.imap(operator.itemgetter(1), zip(range(n), iter))
Edit: changed map() to itertools.imap() in head(). Edit 2: simplified reversed_blocks(). Edit 3: avoid rescanning tail for newlines.