10

当您使用 f.next() 遍历文件时,Python 的 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() 获得的位置。

在使用 readline() 遍历文件时,我之前使用了 seek/tell技巧来倒回一行。使用 next() 时有没有办法倒回一行?

4

3 回答 3

12

不。我会制作一个适配器,主要转发所有呼叫,但在您这样做时保留最后一行的副本,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()

这不会太难扩展到允许您备份多个值的东西。

于 2010-08-21T21:46:56.147 回答
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):
    ...
于 2010-08-21T21:45:02.747 回答
1

Python 的文件迭代器会进行大量缓冲,从而将文件中的位置提前到迭代之前。如果你想使用file.tell(),你必须用“旧方式”来做:

with open(filename) as fileob:
  line = fileob.readline()
  while line:
    print fileob.tell()
    line = fileob.readline()
于 2010-08-21T21:46:35.310 回答