Ecco tre possibilità:
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())
L'esecuzione di questo come lo script principale conferma che le tre funzioni sono equivalenti. Con timeit
(e un * 100
per foo
ottenere stringhe sostanziali per una misurazione più 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
Nota che abbiamo bisogno della list()
chiamata per garantire che gli iteratori siano attraversati, non solo compilati.
IOW, l'implementazione ingenua è molto più veloce che non è nemmeno divertente: 6 volte più veloce del mio tentativo con le find
chiamate, che a sua volta è 4 volte più veloce di un approccio di livello inferiore.
Lezioni da mantenere: la misurazione è sempre una buona cosa (ma deve essere accurata); metodi stringa come splitlines
sono implementati in modi molto veloci; mettere insieme le stringhe programmando a un livello molto basso (specialmente per loop di +=
pezzi molto piccoli) può essere piuttosto lento.
Modifica : aggiunta la proposta di @ Jacob, leggermente modificata per dare gli stessi risultati degli altri (vengono mantenuti gli spazi finali su una riga), ovvero:
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 misurazione dà:
$ python -mtimeit -s'import asp' 'list(asp.f4())'
1000 loops, best of 3: 406 usec per loop
non abbastanza buono come l' .find
approccio basato - comunque, vale la pena tenerlo a mente perché potrebbe essere meno incline a piccoli bug off-by-one (qualsiasi ciclo in cui vedi le occorrenze di +1 e -1, come il mio f3
sopra, dovrebbe automaticamente innescare sospetti off-by-one - e così dovrebbero essere molti loop privi di tali modifiche e dovrebbero averli - anche se credo che anche il mio codice sia corretto poiché sono stato in grado di controllare il suo output con altre funzioni ').
Ma l'approccio basato sulla divisione continua a dominare.
Una nota a parte: forse lo stile migliore per f4
sarebbe:
from cStringIO import StringIO
def f4(foo=foo):
stri = StringIO(foo)
while True:
nl = stri.readline()
if nl == '': break
yield nl.strip('\n')
almeno, è un po 'meno prolisso. La necessità di eliminare le \n
s finali purtroppo proibisce la sostituzione più chiara e veloce del while
ciclo con return iter(stri)
(la iter
parte di cui è ridondante nelle versioni moderne di Python, credo dalla 2.3 o 2.4, ma è anche innocua). Forse vale la pena provare, anche:
return itertools.imap(lambda s: s.strip('\n'), stri)
o variazioni di ciò - ma mi fermo qui poiché è praticamente un esercizio teorico rispetto a quello strip
basato, più semplice e veloce.
foo.splitlines()
giusto?