Aqui estão três possibilidades:
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())
A execução como o script principal confirma que as três funções são equivalentes. Com timeit
(e a * 100
para foo
obter seqüências substanciais para uma medição mais 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
Observe que precisamos da list()
chamada para garantir que os iteradores sejam percorridos, não apenas construídos.
IOW, a implementação ingênua é muito mais rápida e nem engraçada: 6 vezes mais rápida do que minha tentativa de fazer find
chamadas, que por sua vez é 4 vezes mais rápida que uma abordagem de nível inferior.
Lições a reter: a medição é sempre uma coisa boa (mas deve ser precisa); métodos de string como splitlines
são implementados de maneira muito rápida; juntar as strings programando em um nível muito baixo (especialmente por loops de +=
peças muito pequenas) pode ser bastante lento.
Edit : adicionou a proposta de Jacob, ligeiramente modificada para dar os mesmos resultados que os outros (os espaços em branco em uma linha são mantidos), ou seja:
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
A medição fornece:
$ python -mtimeit -s'import asp' 'list(asp.f4())'
1000 loops, best of 3: 406 usec per loop
não é tão bom quanto a .find
abordagem baseada - ainda assim, lembre-se, pois pode ser menos propenso a pequenos erros pontuais (qualquer loop em que você vê ocorrências de +1 e -1, como o meu f3
acima, deve automaticamente desencadear suspeitas de um por um - e o mesmo acontece com muitos loops que não possuem esses ajustes e devem tê-los - embora eu acredite que meu código também esteja correto, pois fui capaz de verificar sua saída com outras funções ').
Mas a abordagem baseada em divisão ainda domina.
Um aparte: possivelmente um estilo melhor f4
seria:
from cStringIO import StringIO
def f4(foo=foo):
stri = StringIO(foo)
while True:
nl = stri.readline()
if nl == '': break
yield nl.strip('\n')
pelo menos, é um pouco menos detalhado. \n
Infelizmente, a necessidade de remover os trailing s proíbe a substituição mais clara e rápida do while
loop return iter(stri)
(a iter
parte da qual é redundante nas versões modernas do Python, acredito que desde 2.3 ou 2.4, mas também é inócua). Talvez valha a pena tentar também:
return itertools.imap(lambda s: s.strip('\n'), stri)
ou variações - mas estou parando aqui, já que é praticamente um exercício teórico strip
baseado no mais simples, e rápido.
foo.splitlines()
certo?