2010-08-21 6 views
9

f.tell de Python no funciona como esperaba cuando iterar sobre un archivo con f.next():Python: rebobinado de una línea en el archivo cuando se repite con 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 

miradas al igual que le da la posición del búfer en lugar de la posición de lo que acaba de obtener con next().

He utilizado previamente el seek/tell trick para rebobinar una línea al iterar sobre un archivo con readline(). ¿Hay alguna manera de rebobinar una línea cuando usas next()?

Respuesta

12

No. Yo haría un adaptador que gran parte reenvía todas las llamadas, pero mantuvo una copia de la última línea cuando lo hizo y luego next le permite llamar a un método diferente para hacer que esa línea salga de nuevo.

De hecho, haría que el adaptador sea un adaptador que podría envolver cualquier iterable en lugar de un contenedor para el archivo porque parece que sería útil en otros contextos.

La sugerencia de Alex de usar el adaptador itertools.tee también funciona, pero creo que escribir su propio adaptador de iterador para manejar este caso en general sería más limpio.

Aquí se muestra un ejemplo:

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

Esto no sería demasiado duro para extender en algo que permite hacer copias de seguridad en más de un solo valor.

+0

Esta es la mejor solución para mí. Ya tenía algo así como un envoltorio, así que fue fácil modificarlo de esta manera. –

+1

Actualización para python3: use '__next__' en lugar de next y este ejemplo funcionará. Consulte http://getpython3.com/diveintopython3/porting-code-to-python-3-with-2to3.html#next –

5

itertools.tee es probablemente el enfoque menos malo: no se puede "derrotar" el almacenamiento en búfer al iterar en el archivo (ni se desea: los efectos de rendimiento serían terribles), manteniendo dos iteradores, uno "un paso detrás" del otro, parece la mejor solución para mí.

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

iterador archivo de Python hace un montón de almacenamiento temporal, avanzando de esta forma la posición en el archivo muy por delante de su iteración. Si desea utilizar file.tell() hay que hacerlo "a la antigua usanza":

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