Python:使用f.next()迭代时回退一行文件

11 投票
3 回答
8015 浏览
提问于 2025-04-16 03:07

在使用Python的f.next()遍历文件时,f.tell的表现并不是我预期的那样:

>>> f=open(".bash_profile", "r")
>>> f.tell()
0
>>> f.next()
"alias rm='rm -i'\n"
>>> f.tell()
397
>>> f.next()
"alias cp='cp -i'\n"
>>> f.tell()
397
>>> f.next()
"alias mv='mv -i'\n"
>>> f.tell()
397

看起来它给出的其实是缓冲区的位置,而不是你刚刚通过next()获取的内容的位置。

我之前用过seek/tell的技巧,可以在用readline()遍历文件时回退一行。那么在使用next()时,有没有办法回退一行呢?

3 个回答

1

Python的文件迭代器会进行很多缓冲,这样在你遍历文件的时候,文件的位置会提前移动得很远。如果你想使用 file.tell() 来查看当前的位置,你必须用“老办法”来做:

with open(filename) as fileob:
  line = fileob.readline()
  while line:
    print fileob.tell()
    line = fileob.readline()
5

itertools.tee 可能是最好的方法了——你无法“打败”在文件上迭代时所做的缓存(而且你也不想这样做:这样会影响性能),所以保持两个迭代器,一个“落后”于另一个,似乎是最合理的解决方案。

import itertools as it

with open('a.txt') as f:
  f1, f2 = it.tee(f)
  f2 = it.chain([None], f2)
  for thisline, prevline in it.izip(f1, f2):
    ...
13

不,我会做一个适配器,这个适配器主要是把所有的调用都转发过去,但在你执行 next 的时候,保存下最后一行的内容,然后你可以调用一个不同的方法,把那一行再拿出来。

我其实会让这个适配器可以包装任何可迭代的对象,而不仅仅是文件,因为这样在其他场景下也会经常用到。

Alex 提出的使用 itertools.tee 适配器的建议也可以,但我觉得自己写一个迭代器适配器来处理这种情况会更简洁。

这里有一个例子:

class rewindable_iterator(object):
    not_started = object()

    def __init__(self, iterator):
        self._iter = iter(iterator)
        self._use_save = False
        self._save = self.not_started

    def __iter__(self):
        return self

    def next(self):
        if self._use_save:
            self._use_save = False
        else:
            self._save = self._iter.next()
        return self._save

    def backup(self):
        if self._use_save:
            raise RuntimeError("Tried to backup more than one step.")
        elif self._save is self.not_started:
            raise RuntimeError("Can't backup past the beginning.")
        self._use_save = True


fiter = rewindable_iterator(file('file.txt', 'r'))
for line in fiter:
    result = process_line(line)
    if result is DoOver:
        fiter.backup()

这个功能其实不难扩展,让你可以备份的不止一个值。

撰写回答