2010-08-21 30 views
9

de f.tell Python ne fonctionne pas comme je m'y attendais lorsque vous itérer sur un fichier avec f.next():Python: une ligne rembobiner dans le fichier lors de l'itération avec 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 

Looks comme il vous donne la position du tampon plutôt que la position de ce que vous venez de recevoir avec next().

J'ai déjà utilisé le/seek trick pour rembobiner une ligne lors de l'itérer sur un fichier avec readline(). Y at-il un moyen de rembobiner une ligne lors de l'utilisation de next()?

Répondre

12

Non, je ferais un adaptateur largement transmis tous les appels, mais a gardé une copie de la dernière ligne lorsque vous avez fait next et laissez-vous appeler une méthode différente pour faire ressortir cette ligne.

Je voudrais réellement faire de l'adaptateur un adaptateur qui pourrait envelopper n'importe quel itérable au lieu d'un wrapper pour le fichier, car cela semble être utile dans d'autres contextes.

La suggestion d'Alex d'utiliser l'adaptateur itertools.tee fonctionne également, mais je pense que l'écriture de votre propre adaptateur d'itérateur pour gérer ce cas en général serait plus propre.

est un exemple:

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

Ce ne serait pas trop difficile de se prolonger dans quelque chose qui vous a permis de sauvegarder par plus d'une valeur.

+0

C'est la meilleure solution pour moi. J'avais déjà quelque chose comme un emballage, donc c'était facile de le modifier de cette façon. –

+1

Mise à jour pour python3: utilisez '__next__' à la place de next et cet exemple fonctionnera. Voir http://getpython3.com/diveintopython3/porting-code-to-python-3-with-2to3.html#next –

5

itertools.tee est probablement la moins mauvaise approche - vous ne pouvez pas "vaincre" la mise en mémoire tampon faite par itération sur le fichier (et vous ne voulez pas: les effets de performance seraient terribles), gardant ainsi deux itérateurs, un "un pas en arrière" de l'autre, semble la solution la plus saine pour moi.

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

fichier de Python iterator fait beaucoup de mise en mémoire tampon, avançant ainsi la position dans le fichier loin de votre itération. Si vous souhaitez utiliser file.tell() vous devez le faire « à l'ancienne »:

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