44

I'm browsing through a Python file pointer of a text file in read-only mode using file.readline() looking for a special line. Once I find that line I want to pass the file pointer to a method that is expecting the file pointer to be at the START of that readline (not right after it.)

How do I essentially undo one file.readline() operation on a file pointer?

codeforester
  • 39,467
  • 16
  • 112
  • 140
MikeN
  • 45,039
  • 49
  • 151
  • 227

5 Answers5

64

You have to remember the position by calling file.tell() before the readline and then calling file.seek() to rewind. Something like:

fp = open('myfile')
last_pos = fp.tell()
line = fp.readline()
while line != '':
  if line == 'SPECIAL':
    fp.seek(last_pos)
    other_function(fp)
    break
  last_pos = fp.tell()
  line = fp.readline()

I can't recall if it is safe to call file.seek() inside of a for line in file loop so I usually just write out the while loop. There is probably a much more pythonic way of doing this.

D.Shawley
  • 58,213
  • 10
  • 98
  • 113
  • 4
    It doesn't seem to be safe to call in a for line in file loop. "OSError: telling position disabled by next() call" – Jiminion Mar 11 '16 at 19:29
  • @Jiminion: Yeah, you have to use `.readline()` (which is slightly less efficient, but preserves necessary positional information) for that to work. You can still write a loop over lines pretty easily though, either with the walrus in more modern Python, `while line := fp.readline():`, or with two-arg `iter` (on any version) `for line in iter(fp.readline, ''):` (change `''` to `b''` if the file is opened in binary mode). – ShadowRanger Mar 26 '23 at 15:43
15

You record the starting point of the line with thefile.tell() before you call readline, and get back to that point, if you need to, with thefile.seek.

>>> with open('bah.txt', 'w') as f:
...   f.writelines('Hello %s\n' % i for i in range(5))
... 
>>> with open('bah.txt') as f:
...   f.readline()
...   x = f.tell()
...   f.readline()
...   f.seek(x)
...   f.readline()
... 
'Hello 0\n'
'Hello 1\n'
'Hello 1\n'
>>> 

as you see, the seek/tell "pair" is "undoing", so to speak, the file pointer movement performed by readline. Of course, this can only work on an actual seekable (i.e., disk) file, not (e.g.) on file-like objects built w/the makefile method of sockets, etc etc.

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
6

If your method simply wants to iterate through the file, then you could use itertools.chain to make an appropriate iterator:

import itertools

# do something to the marker line and everything after
def process(it):
    for line in it:
        print line,
        
with open(filename,'r') as f:
    for line in f:
        if 'marker' in line:
            it=itertools.chain((line,),f)
            process(it)
            break
idbrii
  • 10,975
  • 5
  • 66
  • 107
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
1
fin = open('myfile')
for l in fin:
    if l == 'myspecialline':
        # Move the pointer back to the beginning of this line
        fin.seek(fin.tell() - len(l))
        break
# now fin points to the start of your special line
GWW
  • 43,129
  • 11
  • 115
  • 108
  • I'm thinking along the same lines as you, but the file.seek help suggests otherwise: `If the file is opened in text mode, only offsets returned by tell() are legal.` – Dana the Sane Aug 17 '10 at 19:06
  • I've used this before a few times and haven't had any problems, I'm assuming his has something to do with other character encodings – GWW Aug 17 '10 at 19:27
  • 1
    in text mode, `\r\n` is going to translate to `\n` so you will lose a character. In this case, that character would be the CR (`\r`) so no one notices ;) – D.Shawley Aug 24 '10 at 03:05
0

If you don't know the last line because you didn't visit it you can read backwards until you see a newline character:

with open(logfile, 'r') as f:
    # go to EOF
    f.seek(0, os.SEEK_END)
    nlines = f.tell()
    i=0
    while True:
        f.seek(nlines-i)
        char = f.read(1)
        if char=='\n':
            break
        i+=1
JLT
  • 712
  • 9
  • 15