Oto trzy możliwości:
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())
Uruchomienie tego jako głównego skryptu potwierdza, że te trzy funkcje są równoważne. Z timeit
(a * 100
dla foo
uzyskać znaczne ciągi dla bardziej precyzyjnego pomiaru):
$ 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
Zauważ, że potrzebujemy list()
wywołania, aby upewnić się, że iteratory są przetwarzane, a nie tylko budowane.
IOW, naiwna implementacja jest o wiele szybsza, nawet nie jest zabawna: 6 razy szybsza niż moja próba z find
połączeniami, która z kolei jest 4 razy szybsza niż podejście niższego poziomu.
Lekcje do zapamiętania: pomiar jest zawsze dobry (ale musi być dokładny); metody łańcuchowe, takie jak, splitlines
są implementowane bardzo szybko; składanie łańcuchów razem przez programowanie na bardzo niskim poziomie (szczególnie przez pętle +=
bardzo małych elementów) może być dość powolne.
Edycja : dodano propozycję @ Jacoba, nieznacznie zmodyfikowaną, aby uzyskać takie same wyniki, jak pozostałe (zachowane są końcowe spacje w linii), tj .:
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
Pomiar daje:
$ python -mtimeit -s'import asp' 'list(asp.f4())'
1000 loops, best of 3: 406 usec per loop
nie tak dobre, jak .find
podejście oparte - nadal warto o tym pamiętać, ponieważ może być mniej podatne na małe błędy, które nie są po jednym (każda pętla, w której widzisz wystąpienia +1 i -1, tak jak f3
powyżej, powinna automatycznie wywołują podejrzenia - podobnie jak wiele pętli, które nie mają takich poprawek i powinny je mieć - chociaż uważam, że mój kod jest również poprawny, ponieważ mogłem sprawdzić jego wyjście za pomocą innych funkcji '').
Jednak podejście oparte na podziale nadal obowiązuje.
Na marginesie: prawdopodobnie lepszym stylem f4
byłoby:
from cStringIO import StringIO
def f4(foo=foo):
stri = StringIO(foo)
while True:
nl = stri.readline()
if nl == '': break
yield nl.strip('\n')
przynajmniej jest trochę mniej rozwlekły. Konieczność \n
usunięcia końcowych s niestety zabrania jaśniejszej i szybszej zamiany while
pętli na return iter(stri)
(ta iter
część jest zbędna we współczesnych wersjach Pythona, myślę, że od 2.3 lub 2.4, ale jest również nieszkodliwa). Może warto spróbować, także:
return itertools.imap(lambda s: s.strip('\n'), stri)
lub ich odmiany - ale zatrzymuję się tutaj, ponieważ jest to właściwie ćwiczenie teoretyczne strip
oparte na podstawowym, najprostszym i najszybszym.
foo.splitlines()
prawda?