Python:使用f.next()迭代时回退一行文件
在使用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()
这个功能其实不难扩展,让你可以备份的不止一个值。