Python: перематывать одну строку в файле при итерации с помощью f.next () - PullRequest
10 голосов
/ 22 августа 2010

f.tell Python не работает так, как я ожидал, когда вы перебираете файл с помощью f.next ():

>>> 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 (). Есть ли способ перемотать одну строку при использовании next ()?

Ответы [ 3 ]

12 голосов
/ 22 августа 2010

Нет. Я бы сделал адаптер, который в основном перенаправлял бы все вызовы, но сохранял копию последней строки, когда вы сделали next, а затем позволял бы вам вызывать другой метод, чтобы эта строка снова всплывала.

Я бы на самом деле сделал адаптер адаптером, который мог бы обернуть любую итерацию вместо оболочки для файла, потому что это звучит так, как будто это часто будет полезно в других контекстах.

Предложение Алекса об использовании 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()

Это не будет слишком сложно расширить на что-то, что позволит вам делать резервные копии более чем на одно значение.

5 голосов
/ 22 августа 2010

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):
    ...
1 голос
/ 22 августа 2010

Файловый итератор Python выполняет большую буферизацию, тем самым продвигая позицию в файле намного впереди вашей итерации. Если вы хотите использовать file.tell(), вы должны сделать это «по-старому»:

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