Aquí hay tres posibilidades:
foo = """
this is
a multi-line string.
"""
def f1(foo=foo): return iter(foo.splitlines())
def f2(foo=foo):
retval = ''
for char in foo:
retval += char if not char == '\n' else ''
if char == '\n':
yield retval
retval = ''
if retval:
yield retval
def f3(foo=foo):
prevnl = -1
while True:
nextnl = foo.find('\n', prevnl + 1)
if nextnl < 0: break
yield foo[prevnl + 1:nextnl]
prevnl = nextnl
if __name__ == '__main__':
for f in f1, f2, f3:
print list(f())
Ejecutar esto como el script principal confirma que las tres funciones son equivalentes. Con timeit
(y * 100
para foo
obtener cadenas sustanciales para una medición más precisa):
$ python -mtimeit -s'import asp' 'list(asp.f3())'
1000 loops, best of 3: 370 usec per loop
$ python -mtimeit -s'import asp' 'list(asp.f2())'
1000 loops, best of 3: 1.36 msec per loop
$ python -mtimeit -s'import asp' 'list(asp.f1())'
10000 loops, best of 3: 61.5 usec per loop
Tenga en cuenta que necesitamos la list()
llamada para asegurarnos de que los iteradores se atraviesan, no solo se compilan.
IOW, la implementación ingenua es mucho más rápida que ni siquiera es divertida: 6 veces más rápido que mi intento con find
llamadas, que a su vez es 4 veces más rápido que un enfoque de nivel inferior.
Lecciones para retener: la medición siempre es algo bueno (pero debe ser precisa); los métodos de cadena como splitlines
se implementan de manera muy rápida; juntar cadenas programando a un nivel muy bajo (especialmente mediante bucles de +=
piezas muy pequeñas) puede ser bastante lento.
Editar : se agregó la propuesta de @ Jacob, ligeramente modificada para dar los mismos resultados que los demás (se mantienen los espacios en blanco al final de una línea), es decir:
from cStringIO import StringIO
def f4(foo=foo):
stri = StringIO(foo)
while True:
nl = stri.readline()
if nl != '':
yield nl.strip('\n')
else:
raise StopIteration
La medición da:
$ python -mtimeit -s'import asp' 'list(asp.f4())'
1000 loops, best of 3: 406 usec per loop
no es tan bueno como el .find
enfoque basado; aún así, vale la pena tenerlo en cuenta porque podría ser menos propenso a pequeños errores uno por uno (cualquier bucle en el que vea apariciones de +1 y -1, como el f3
anterior, debería automáticamente desencadenar sospechas de uno en uno, y también deberían hacerlo muchos bucles que carecen de tales ajustes y deberían tenerlos, aunque creo que mi código también es correcto ya que pude verificar su salida con otras funciones ').
Pero el enfoque basado en la división aún prevalece.
Un aparte: posiblemente un mejor estilo para f4
sería:
from cStringIO import StringIO
def f4(foo=foo):
stri = StringIO(foo)
while True:
nl = stri.readline()
if nl == '': break
yield nl.strip('\n')
al menos, es un poco menos detallado. La necesidad de eliminar los trailing \n
s desafortunadamente prohíbe el reemplazo más claro y rápido del while
bucle con return iter(stri)
(la iter
parte de la cual es redundante en las versiones modernas de Python, creo que desde 2.3 o 2.4, pero también es inocuo). Quizás valga la pena intentarlo, también:
return itertools.imap(lambda s: s.strip('\n'), stri)
o variaciones de los mismos, pero me detengo aquí, ya que es más o menos un ejercicio teórico que es el strip
más simple y rápido.
foo.splitlines()
¿no?