Voici trois possibilités:
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'exécution de ceci comme le script principal confirme que les trois fonctions sont équivalentes. Avec timeit
(et un * 100
for foo
pour obtenir des chaînes substantielles pour une mesure plus précise):
$ 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
Notez que nous avons besoin de l' list()
appel pour nous assurer que les itérateurs sont parcourus, pas seulement construits.
IOW, l'implémentation naïve est tellement plus rapide qu'elle n'est même pas drôle: 6 fois plus rapide que ma tentative avec des find
appels, qui à son tour est 4 fois plus rapide qu'une approche de niveau inférieur.
Leçons à retenir: la mesure est toujours une bonne chose (mais doit être précise); les méthodes de chaîne comme splitlines
sont implémentées de manière très rapide; assembler des chaînes en programmant à un niveau très bas (en particulier par des boucles +=
de très petits morceaux) peut être assez lent.
Edit : ajout de la proposition de @ Jacob, légèrement modifiée pour donner les mêmes résultats que les autres (les blancs de fin sur une ligne sont conservés), soit:
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 mesure donne:
$ python -mtimeit -s'import asp' 'list(asp.f4())'
1000 loops, best of 3: 406 usec per loop
pas tout à fait aussi bon que l' .find
approche basée - encore, à garder à l'esprit car il pourrait être moins sujet à de petits bugs ponctuels (toute boucle où vous voyez des occurrences de +1 et -1, comme celle f3
ci - dessus, devrait automatiquement déclencher des soupçons ponctuels - et il en va de même pour de nombreuses boucles qui n'ont pas de tels réglages et devraient les avoir - bien que je pense que mon code est également correct puisque j'ai pu vérifier sa sortie avec d'autres fonctions ').
Mais l'approche basée sur la division est toujours d'actualité.
Un aparté: peut-être un meilleur style pour f4
serait:
from cStringIO import StringIO
def f4(foo=foo):
stri = StringIO(foo)
while True:
nl = stri.readline()
if nl == '': break
yield nl.strip('\n')
au moins, c'est un peu moins verbeux. La nécessité de supprimer les trailing \n
s interdit malheureusement le remplacement plus clair et plus rapide de la while
boucle par return iter(stri)
(la iter
partie dont est redondante dans les versions modernes de Python, je crois depuis 2.3 ou 2.4, mais c'est aussi inoffensif). Cela vaut peut-être la peine d'essayer, aussi:
return itertools.imap(lambda s: s.strip('\n'), stri)
ou des variations de celui-ci - mais je m'arrête ici car c'est à peu près un exercice théorique pour le strip
plus simple et le plus rapide.
foo.splitlines()
non?