130 votes

Itérer sur les lignes d'une chaîne

J'ai un multi-ligne de chaîne définie comme ceci:

foo = """
this is 
a multi-line string.
"""

Cette chaîne nous servir de test d'entrée pour un analyseur je suis en train d'écrire. L'analyseur-fonction reçoit un file-objet que l'entrée et la parcourt. Il fait également appel à l' next() méthode directement à sauter des lignes, donc j'ai vraiment besoin d'un itérateur en entrée, pas un objet iterable. J'ai besoin d'un itérateur qui effectue une itération sur les différentes lignes de cette chaîne comme un file-objet sur les lignes d'un fichier texte. Je pourrais bien sûr faire comme ceci:

lineiterator = iter(foo.splitlines())

Est-il un moyen plus direct de faire cela? Dans ce scénario, la chaîne a traversé une fois pour le fractionnement, puis de nouveau par l'analyseur. Il n'a pas d'importance dans mon test, puisque la chaîne est très court il y a, je suis juste d'avoir de la curiosité. Python a tellement utile et efficace built-ins pour ce genre de chose, mais je ne trouve rien qui correspond à ce besoin.

152voto

Alex Martelli Points 330805

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 ce que le script principal, confirme les trois fonctions sont équivalentes. Avec timeit (et un * 100 pour foo pour obtenir substantielle des chaînes 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

Note nous avons besoin de l' list() appel pour s'assurer les itérateurs sont parcourus, pas seulement construit.

OIE, de la naïveté de la mise en œuvre est beaucoup plus rapide, il n'est même pas drôle: 6 fois plus rapide que ma tentative avec find des appels, qui à son tour est 4 fois plus rapide qu'un faible niveau d'approche.

Leçons à retenir: la mesure est toujours une bonne chose (mais doit être exacte); string méthodes comme splitlines sont mis en œuvre très rapide; de la mise chaînes par programmation à un niveau très faible (de l'esp. par des boucles d' += de très petits morceaux) peut être assez lente.

Edit: ajout de @Jacob proposition, légèrement modifié afin de donner les mêmes résultats que les autres (les espaces à droite sur une ligne, sont conservés), c'est à dire:

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 fondée sur -- encore, la peine de garder à l'esprit, car elle pourrait être moins enclins à petit tout-en-un bugs (une boucle où vous pouvez voir les occurrences de +1 et -1, comme mon f3 - dessus, devrait déclencher automatiquement tout-en-un soupçons -- et de nombreuses boucles qui manquent de ces réglages et devrait disposer d'eux-même si je crois que mon code est aussi la droite depuis que j'ai été en mesure de vérifier sa sortie avec d'autres fonctions).

Mais le split-approche fondée sur les règles.

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 la bande de fuite \ns malheureusement interdit la plus claire et la plus rapide de remplacement de l' while boucle avec return iter(stri) ( iter partie de quoi est redondante dans les versions modernes de Python, je crois que depuis 2.3 ou 2.4, mais c'est aussi inoffensif). Peut-être la peine d'essayer, aussi:

    return itertools.imap(lambda s: s.strip('\n'), stri)

ou des variations de ceux-ci-mais j'arrête ici, puisque c'est à peu près un exercice théorique wrt l' strip base, la plus simple et la plus rapide, un.

55voto

Brian Points 48423

Je ne suis pas sûr de ce que vous entendez par "puis de nouveau par l'analyseur". Après le découpage a été fait, il n'y a pas plus loin de la traversée de la chaîne, seulement un parcours de la liste de fractionnement des chaînes de caractères. Cela va probablement être le moyen le plus rapide pour ce faire, aussi longtemps que la taille de votre chaîne n'est pas absolument énorme. Le fait que python utilise les cordes immuables signifie que vous devez toujours créer une nouvelle chaîne, de sorte que ce qui doit être fait à un certain point, de toute façon.

Si votre chaîne est très grand, l'inconvénient est l'utilisation de la mémoire: vous aurez la chaîne d'origine et une liste de fractionnement des chaînes dans la mémoire, dans le même temps, le doublement de la mémoire requise. Un itérateur approche peut vous sauver la présente, la construction d'une chaîne de caractères que nécessaire, même s'il paie toujours le "fractionnement" de pénalité. Toutefois, si votre chaîne est que, en général, vous voulez éviter même l' annulation de la division de chaîne dans la mémoire. Il serait préférable de simplement lire la chaîne de caractères à partir d'un fichier, ce qui vous permet déjà d'itérer à travers elle que les lignes.

Toutefois, si vous avez une énorme chaîne de caractères dans la mémoire déjà, une approche possible serait d'utiliser StringIO, qui présente un fichier d'interface semblable à une chaîne de caractères, y compris en permettant l'itération en ligne (en interne .trouver pour trouver le prochain saut de ligne). Vous obtenez alors:

import StringIO
s = StringIO.StringIO(myString)
for line in s:
    do_something_with(line)

3voto

Jacob Oscarson Points 4275

Si je lis correctement Modules/cStringIO.c , cela devrait être assez efficace (bien qu'un peu verbeux):

 from cStringIO import StringIO

def iterbuf(buf):
    stri = StringIO(buf)
    while True:
        nl = stri.readline()
        if nl != '':
            yield nl.strip()
        else:
            raise StopIteration
 

1voto

Wayne Werner Points 10172

Je suppose que vous pourriez rouler le vôtre:

 def parse(string):
    retval = ''
    for char in string:
        retval += char if not char == '\n' else ''
        if char == '\n':
            yield retval
            retval = ''
    if retval:
        yield retval
 

Je ne suis pas sûr de l'efficacité de cette implémentation, mais cela ne fera qu'une itération sur votre chaîne.

Mmm, générateurs.

Modifier:

Bien sûr, vous voudrez également ajouter le type d’actions d’analyse que vous souhaitez entreprendre, mais c’est assez simple.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X