J'ai une chaîne multiligne définie comme ceci:
foo = """
this is
a multi-line string.
"""
Cette chaîne que nous avons utilisée comme entrée de test pour un analyseur que j'écris. La fonction parseur reçoit un file
-objet en entrée et effectue une itération dessus. Il appelle également la next()
méthode directement pour sauter des lignes, donc j'ai vraiment besoin d'un itérateur en entrée, pas d'un itérateur. J'ai besoin d'un itérateur qui itère sur les lignes individuelles de cette chaîne comme un file
-objet le ferait sur les lignes d'un fichier texte. Je pourrais bien sûr le faire comme ceci:
lineiterator = iter(foo.splitlines())
Existe-t-il une manière plus directe de procéder? Dans ce scénario, la chaîne doit traverser une fois pour le fractionnement, puis à nouveau par l'analyseur. Cela n'a pas d'importance dans mon cas de test, puisque la chaîne est très courte là-bas, je demande juste par curiosité. Python a tellement de composants intégrés utiles et efficaces pour ce genre de choses, mais je n'ai rien trouvé qui réponde à ce besoin.
foo.splitlines()
non?splitlines()
et une seconde fois en itérant sur le résultat de cette méthode.Réponses:
Voici trois possibilités:
L'exécution de ceci comme le script principal confirme que les trois fonctions sont équivalentes. Avec
timeit
(et un* 100
forfoo
pour obtenir des chaînes substantielles pour une mesure plus précise):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:
La mesure donne:
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 cellef3
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: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 lawhile
boucle parreturn iter(stri)
(laiter
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: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.la source
(line[:-1] for line in cStringIO.StringIO(foo))
c'est assez rapide; presque aussi rapide que l'implémentation naïve, mais pas tout à fait.timeit
une habitude.list
appel pour chronométrer toutes les parties pertinentes! -).split()
troque clairement la mémoire pour la performance, tenant une copie de toutes les sections en plus des structures de la liste.Je ne sais pas ce que vous entendez par "puis encore par l'analyseur". Une fois le fractionnement effectué, il n'y a plus de parcours de la chaîne , seulement un parcours de la liste des chaînes fractionnées. Ce sera probablement le moyen le plus rapide d'accomplir cela, tant que la taille de votre chaîne n'est pas absolument énorme. Le fait que python utilise des chaînes immuables signifie que vous devez toujours créer une nouvelle chaîne, donc cela doit être fait à un moment donné de toute façon.
Si votre chaîne est très volumineuse, l'inconvénient réside dans l'utilisation de la mémoire: vous aurez la chaîne d'origine et une liste de chaînes fractionnées en mémoire en même temps, doublant la mémoire requise. Une approche itératrice peut vous épargner cela, en construisant une chaîne selon vos besoins, même si elle paie toujours la pénalité de "fractionnement". Cependant, si votre chaîne est aussi grande, vous souhaitez généralement éviter même la chaîne non divisée en mémoire. Il serait préférable de simplement lire la chaîne à partir d'un fichier, ce qui vous permet déjà de l'itérer sous forme de lignes.
Cependant, si vous avez déjà une énorme chaîne en mémoire, une approche serait d'utiliser StringIO, qui présente une interface de type fichier à une chaîne, y compris en permettant l'itération par ligne (en utilisant .find en interne pour trouver la prochaine nouvelle ligne). Vous obtenez alors:
la source
io
package pour cela, par exemple utiliser à laio.StringIO
place deStringIO.StringIO
. Voir docs.python.org/3/library/io.htmlStringIO
est également un bon moyen d'obtenir une gestion de nouvelle ligne universelle haute performance.Si je lis
Modules/cStringIO.c
correctement, cela devrait être assez efficace (bien qu'un peu verbeux):la source
La recherche basée sur Regex est parfois plus rapide que l'approche du générateur:
la source
Je suppose que vous pouvez rouler le vôtre:
Je ne suis pas sûr de l'efficacité de cette implémentation, mais cela ne répétera qu'une seule fois sur votre chaîne.
Mmm, générateurs.
Éditer:
Bien sûr, vous voudrez également ajouter le type d'actions d'analyse que vous souhaitez effectuer, mais c'est assez simple.
la source
+=
partie a les piresO(N squared)
performances, bien que plusieurs astuces d'implémentation essaient de réduire cela lorsque cela est possible)..join
méthode ressemble en fait à une complexité O (N). Comme je n'ai pas encore trouvé la comparaison particulière faite sur SO, j'ai commencé une question stackoverflow.com/questions/3055477/… (qui a étonnamment reçu plus de réponses que les miennes!)Vous pouvez parcourir "un fichier", qui produit des lignes, y compris le caractère de nouvelle ligne de fin. Pour créer un "fichier virtuel" à partir d'une chaîne, vous pouvez utiliser
StringIO
:la source