Ось три можливості:
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())
Запустивши це як основний сценарій, підтверджує, що три функції є рівнозначними. З timeit
(і * 100
для, foo
щоб отримати істотні рядки для більш точного вимірювання):
$ 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
Зауважте, нам потрібен list()
дзвінок, щоб переконатися, що ітератори проходять, а не просто будуються.
IOW, наївна реалізація настільки швидша, що це навіть не смішно: у 6 разів швидше, ніж моя спроба find
дзвінками, що в свою чергу в 4 рази швидше, ніж підхід нижчого рівня.
Уроки, які слід зберегти: вимірювання - це завжди добре (але повинно бути точним); такі струнні методи splitlines
реалізуються дуже швидко; з'єднання рядків програмуванням на дуже низькому рівні (наприклад, за циклами +=
дуже маленьких шматочків) може бути досить повільним.
Редагувати : додано пропозицію @ Якова, трохи змінену, щоб дати ті самі результати, що й інші (тривалі пробіли на рядку зберігаються), тобто:
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
Вимірювання дає:
$ python -mtimeit -s'import asp' 'list(asp.f4())'
1000 loops, best of 3: 406 usec per loop
не так добре, як .find
базований підхід - все ж, варто пам’ятати, оскільки це може бути менш схильним до дрібних помилок один за одним (будь-який цикл, де ви бачите випадки +1 і -1, як у менеf3
версії, повинен автоматично викликати підозри один за одним - і так має бути багато циклів, у яких відсутні такі налаштування і повинні їх мати, - хоча я вважаю, що мій код також є правильним, оскільки мені вдалося перевірити його вихід за допомогою інших функцій ').
Але підхід на основі розколу все ще правила.
Відмовою: можливо, кращим стилем для f4
буде:
from cStringIO import StringIO
def f4(foo=foo):
stri = StringIO(foo)
while True:
nl = stri.readline()
if nl == '': break
yield nl.strip('\n')
принаймні, це трохи менш багатослівний. На \n
жаль, необхідність знімати зачіпання s, на жаль, забороняє чіткішу та швидшу заміну while
петлі return iter(stri)
( iter
частина, в якій вона є надмірною в сучасних версіях Python, я вважаю, що з 2.3 або 2.4, але це також нешкідливо). Можливо, варто спробувати також:
return itertools.imap(lambda s: s.strip('\n'), stri)
або його варіації - але я зупиняюся тут, оскільки це майже теоретична вправа, strip
заснована на найпростішій та найшвидшій.
foo.splitlines()
правильно?