ActiveState Code

Recipe 439045: Read a text file backwards (yet another implementation)


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

Python
 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)

Discussion

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

Comments

  1. 1. At 4:40 a.m. on 7 jan 2006, Raymond Hettinger said:

    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)

Sign in to comment