Welcome, guest | Sign In | My Account | Store | Cart

Yet another way to read a file line by line, starting at the end.

Python, 54 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#!/usr/bin/env python
# -*-mode: python; coding: iso-8859-1 -*-
#
# Copyright (c) Peter Astrand <astrand@cendio.se>

import os
import string

class BackwardsReader:
    """Read a file line by line, backwards"""
    BLKSIZE = 4096

    def readline(self):
        while 1:
            newline_pos = string.rfind(self.buf, "\n")
            pos = self.file.tell()
            if newline_pos != -1:
                # Found a newline
                line = self.buf[newline_pos+1:]
                self.buf = self.buf[:newline_pos]
                if pos != 0 or newline_pos != 0 or self.trailing_newline:
                    line += "\n"
                return line
            else:
                if pos == 0:
                    # Start-of-file
                    return ""
                else:
                    # Need to fill buffer
                    toread = min(self.BLKSIZE, pos)
                    self.file.seek(-toread, 1)
                    self.buf = self.file.read(toread) + self.buf
                    self.file.seek(-toread, 1)
                    if pos - toread == 0:
                        self.buf = "\n" + self.buf

    def __init__(self, file):
        self.file = file
        self.buf = ""
        self.file.seek(-1, 2)
        self.trailing_newline = 0
        lastchar = self.file.read(1)
        if lastchar == "\n":
            self.trailing_newline = 1
            self.file.seek(-1, 2)

# Example usage
br = BackwardsReader(open('bar'))

while 1:
    line = br.readline()
    if not line:
        break
    print repr(line)

I know there are several recipes already, but I didn't like them, so I wrote my own implementation.

3 comments

Raymond Hettinger 18 years, 3 months ago  # | flag

Simplifying code transformations. * Converted to a generator

  • Use string methods instead of string module

  • trailing_newline set with a single test

  • Nested if-statements collapsed to if/elif/else

  • Replace var!=0 comparison with simple boolean test

  • Import of os module was unused

    def BackwardsReader(file, BLKSIZE = 4096): """Read a file line by line, backwards"""

    buf = ""
    file.seek(-1, 2)
    lastchar = file.read(1)
    trailing_newline = (lastchar == "\n")
    
    while 1:
        newline_pos = buf.rfind("\n")
        pos = file.tell()
        if newline_pos != -1:
            # Found a newline
            line = buf[newline_pos+1:]
            buf = buf[:newline_pos]
            if pos or newline_pos or trailing_newline:
                line += "\n"
            yield line
        elif pos:
            # Need to fill buffer
            toread = min(BLKSIZE, pos)
            file.seek(-toread, 1)
            buf = file.read(toread) + buf
            file.seek(-toread, 1)
            if pos == toread:
                buf = "\n" + buf
        else:
            # Start-of-file
            return
    

    Example usage

    for line in BackwardsReader(open('brent.txt')): print repr(line)

Kevin German 13 years, 6 months ago  # | flag

Minor change to Hettinger's post to get around limitations with io.seek in Python v3.1 The above fails with:

IOError: can't do nonzero end-relative seeks at
5: file.seek(-1, 2)

and

IOError: can't do nonzero cur-relative seeks at 34: file.seek(-toread, 1) 36: file.seek(-toread, 1)

<code>

def reverseReadFile(file, BLKSIZE = 4096): """Read a file line by line, backwards""" buf = ""

if( not file.seekable() ):
    return

file.seek(0, 2)
lastchar = file.read(1)
trailing_newline = (lastchar == "\n")

while 1:
    newline_pos = buf.rfind("\n")
    pos = file.tell()
    if newline_pos != -1:
        # Found a newline
        line = buf[newline_pos+1:]
        buf = buf[:newline_pos]
        if pos or newline_pos or trailing_newline:
            line += "\n"
        yield line
    elif pos:
        # Need to fill buffer
        toread = min(BLKSIZE, pos)
        file.seek(pos-toread, 0)
        buf = file.read(toread) + buf
        file.seek(pos-toread, 0)
        if pos == toread:
            buf = "\n" + buf
    else:
        # Start-of-file
        return

</code>

Elcimar Leandro 13 years ago  # | flag

Thank you Peter, Raymond and Kevin!

Created by Peter Astrand on Thu, 11 Aug 2005 (PSF)
Python recipes (4591)
Peter Astrand's recipes (1)

Required Modules

Other Information and Tasks