Ich habe eine mehrzeilige Zeichenfolge wie folgt definiert:
foo = """
this is
a multi-line string.
"""
Diese Zeichenfolge haben wir als Testeingabe für einen Parser verwendet, den ich schreibe. Die Parser-Funktion empfängt ein file
-Objekt als Eingabe und iteriert darüber. Es ruft die next()
Methode auch direkt auf, um Zeilen zu überspringen, daher brauche ich wirklich einen Iterator als Eingabe, keinen iterierbaren. Ich brauche einen Iterator, der über die einzelnen Zeilen dieser Zeichenfolge iteriert, wie ein file
Objekt über die Zeilen einer Textdatei. Ich könnte es natürlich so machen:
lineiterator = iter(foo.splitlines())
Gibt es einen direkteren Weg, dies zu tun? In diesem Szenario muss die Zeichenfolge einmal für die Aufteilung und dann erneut vom Parser durchlaufen werden. In meinem Testfall spielt es keine Rolle, da die Saite dort sehr kurz ist, frage ich nur aus Neugier. Python hat so viele nützliche und effiziente integrierte Funktionen für solche Dinge, aber ich konnte nichts finden, das diesem Bedarf entspricht.
foo.splitlines()
oder?splitlines()
und ein zweites Mal durch Iteration über das Ergebnis dieser Methode iteriert wird .Antworten:
Hier sind drei Möglichkeiten:
Wenn Sie dies als Hauptskript ausführen, wird bestätigt, dass die drei Funktionen gleichwertig sind. Mit
timeit
(und einem* 100
fürfoo
, um wesentliche Zeichenfolgen für eine genauere Messung zu erhalten):Beachten Sie, dass wir den
list()
Aufruf benötigen , um sicherzustellen, dass die Iteratoren durchlaufen und nicht nur erstellt werden.IOW, die naive Implementierung ist so viel schneller, dass es nicht einmal lustig ist: 6-mal schneller als mein Versuch mit
find
Anrufen, was wiederum 4-mal schneller ist als ein Ansatz auf niedrigerer Ebene.Zu behaltende Lektionen: Messung ist immer eine gute Sache (muss aber genau sein); String-Methoden wie
splitlines
werden sehr schnell implementiert; Das Zusammensetzen von Saiten durch Programmieren auf einer sehr niedrigen Ebene (insbesondere durch Schleifen+=
sehr kleiner Stücke) kann sehr langsam sein.Bearbeiten : @ Jacobs Vorschlag hinzugefügt, leicht modifiziert, um die gleichen Ergebnisse wie die anderen zu erzielen (nachgestellte Leerzeichen in einer Zeile bleiben erhalten), dh:
Messen ergibt:
Nicht ganz so gut wie der
.find
basierte Ansatz - dennoch sollte man bedenken, dass er möglicherweise weniger anfällig für kleine Fehler ist (jede Schleife, in der Sie Vorkommen von +1 und -1 sehen, wief3
oben, sollte automatisch erfolgen Auslösen von Verdachtsmomenten - und auch viele Schleifen, denen solche Optimierungen fehlen und die sie haben sollten - obwohl ich glaube, dass mein Code auch richtig ist, da ich seine Ausgabe mit anderen Funktionen überprüfen konnte ').Der Split-basierte Ansatz regiert jedoch weiterhin.
Nebenbei: Möglicherweise wäre ein besserer Stil für
f4
:Zumindest ist es etwas weniger ausführlich. Die Notwendigkeit, nachgestellte
\n
s zu entfernen, verhindert leider das klarere und schnellere Ersetzen derwhile
Schleife durchreturn iter(stri)
(deriter
Teil davon ist in modernen Versionen von Python überflüssig, glaube ich seit 2.3 oder 2.4, aber es ist auch harmlos). Vielleicht auch einen Versuch wert:oder Variationen davon - aber ich höre hier auf, da es so ziemlich eine theoretische Übung für die
strip
basierte, einfachste und schnellste ist.quelle
(line[:-1] for line in cStringIO.StringIO(foo))
ist ziemlich schnell; fast so schnell wie die naive Umsetzung, aber nicht ganz.timeit
eine Gewohnheit zu verwenden.list
Anruf benötigen , um alle relevanten Teile tatsächlich zeitlich zu steuern ! -).split()
Tauschen Sie Speicher eindeutig gegen Leistung aus und halten Sie zusätzlich zu den Listenstrukturen eine Kopie aller Abschnitte bereit.Ich bin mir nicht sicher, was du mit "dann wieder mit dem Parser" meinst. Nachdem die Aufteilung durchgeführt wurde, erfolgt keine weitere Durchquerung der Zeichenfolge , sondern nur eine Durchquerung der Liste der geteilten Zeichenfolgen. Dies ist wahrscheinlich der schnellste Weg, um dies zu erreichen, solange die Größe Ihrer Zeichenfolge nicht absolut groß ist. Die Tatsache, dass Python unveränderliche Zeichenfolgen verwendet, bedeutet, dass Sie immer eine neue Zeichenfolge erstellen müssen , sodass dies ohnehin irgendwann erfolgen muss.
Wenn Ihre Zeichenfolge sehr groß ist, liegt der Nachteil in der Speichernutzung: Sie haben gleichzeitig die ursprüngliche Zeichenfolge und eine Liste der geteilten Zeichenfolgen im Speicher, wodurch sich der erforderliche Speicher verdoppelt. Ein Iterator-Ansatz kann Ihnen dies ersparen und nach Bedarf eine Zeichenfolge erstellen, obwohl die Strafe für das "Aufteilen" immer noch gezahlt wird. Wenn Ihre Zeichenfolge jedoch so groß ist, möchten Sie im Allgemeinen vermeiden, dass sich auch die nicht aufgeteilte Zeichenfolge im Speicher befindet. Es ist besser, nur die Zeichenfolge aus einer Datei zu lesen, damit Sie sie bereits als Zeilen durchlaufen können.
Wenn Sie jedoch bereits eine große Zeichenfolge im Speicher haben, besteht ein Ansatz darin, StringIO zu verwenden, das eine dateiähnliche Schnittstelle zu einer Zeichenfolge darstellt, einschließlich des Erlaubens einer zeilenweisen Iteration (intern mit .find, um die nächste neue Zeile zu finden). Sie erhalten dann:
quelle
io
Paket verwenden, z . B.io.StringIO
anstelle vonStringIO.StringIO
. Siehe docs.python.org/3/library/io.htmlStringIO
ist auch ein guter Weg, um ein universelles Newline-Handling mit hoher Leistung zu erhalten.Wenn ich
Modules/cStringIO.c
richtig lese , sollte dies ziemlich effizient sein (obwohl etwas ausführlich):quelle
Die Regex-basierte Suche ist manchmal schneller als der Generator-Ansatz:
quelle
Ich nehme an, Sie könnten Ihre eigenen rollen:
Ich bin mir nicht sicher, wie effizient diese Implementierung ist, aber das wird Ihre Zeichenfolge nur einmal durchlaufen.
Mmm, Generatoren.
Bearbeiten:
Natürlich möchten Sie auch jede Art von Parsing-Aktionen hinzufügen, die Sie ausführen möchten, aber das ist ziemlich einfach.
quelle
+=
Teil weist die schlechtesteO(N squared)
Leistung auf, obwohl mehrere Implementierungstricks versuchen, diese zu senken, wenn dies möglich ist)..join
sieht die Methode tatsächlich nach O (N) -Komplexität aus. Da ich den speziellen Vergleich, der auf SO gemacht wurde, noch nicht finden konnte, startete ich eine Frage stackoverflow.com/questions/3055477/… (die überraschenderweise mehr Antworten erhielt als nur meine eigene!)Sie können über "eine Datei" iterieren, wodurch Zeilen einschließlich des nachgestellten Zeilenumbruchs erzeugt werden. Um eine "virtuelle Datei" aus einer Zeichenfolge zu erstellen, können Sie Folgendes verwenden
StringIO
:quelle