Python:用f.next()迭代时在文件中倒带一行

Python: rewinding one line in file when iterating with f.next()

当您使用f.next()迭代文件时,python的f.tell不能像我预期的那样工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> f=open(".bash_profile","r")
>>> f.tell()
0
>>> f.next()
"alias rm='rm -i'
"

>>> f.tell()
397
>>> f.next()
"alias cp='cp -i'
"

>>> f.tell()
397
>>> f.next()
"alias mv='mv -i'
"

>>> f.tell()
397

看起来它给了您缓冲区的位置,而不是您刚用next()得到的位置。

我以前使用seek/tell技巧在使用readline()迭代文件时倒回一行。在使用next()时,是否有方法倒回一行?


不,我会制作一个适配器,它主要转发所有调用,但在执行next操作时保留最后一行的副本,然后让您调用另一种方法,使该行再次弹出。

实际上,我将使适配器成为一个可以包装任何iterable的适配器,而不是包装文件的适配器,因为听起来它在其他上下文中通常很有用。

Alex关于使用itertools.tee适配器的建议也有效,但我认为编写自己的迭代器适配器来处理这种情况通常会更简单。

下面是一个例子:

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

这不会太难扩展到允许您以一个以上的值进行备份的内容中。


itertools.tee可能是最不糟糕的方法——您不能通过对文件进行迭代来"击败"缓冲(您也不想这样做:性能影响会很糟糕),所以让两个迭代器(一个"落后"一个)对我来说似乎是最合理的解决方案。

1
2
3
4
5
6
7
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):
    ...


python的文件迭代器做了大量的缓冲处理,从而使文件中的位置远远领先于迭代。如果你想用file.tell(),你必须用"老办法"来做:

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