Hier sind drei Möglichkeiten:
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())
Wenn Sie dies als Hauptskript ausführen, wird bestätigt, dass die drei Funktionen gleichwertig sind. Mit timeit
(und einem * 100
für foo
, um wesentliche Zeichenfolgen für eine genauere Messung zu erhalten):
$ 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
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:
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
Messen ergibt:
$ python -mtimeit -s'import asp' 'list(asp.f4())'
1000 loops, best of 3: 406 usec per loop
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, wie f3
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
:
from cStringIO import StringIO
def f4(foo=foo):
stri = StringIO(foo)
while True:
nl = stri.readline()
if nl == '': break
yield nl.strip('\n')
Zumindest ist es etwas weniger ausführlich. Die Notwendigkeit, nachgestellte \n
s zu entfernen, verhindert leider das klarere und schnellere Ersetzen der while
Schleife durch return iter(stri)
(der iter
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:
return itertools.imap(lambda s: s.strip('\n'), stri)
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.
foo.splitlines()
oder?