Короткий ответ
Повторная конкатенация строк через += в цикле на больших объёмах данных квадратично деградирует по времени, так как строки в Python неизменяемы и каждая конкатенация создаёт новую строку, копируя все предыдущие символы; решение — собирать части в список и объединить один раз через ''.join(parts) в конце.
Как это работает подробнее
Строка — неизменяемый объект, поэтому result += chunk не дополняет существующую строку "по месту", а создаёт новый объект, копируя все символы старой строки плюс новый фрагмент. При N итерациях суммарный объём скопированных символов растёт квадратично (1+2+...+N), хотя CPython в некоторых случаях оптимизирует этот паттерн для простых локальных переменных, такая оптимизация не гарантирована языком и не работает надёжно, например, при накоплении в атрибуте объекта или списке.
Пример кода
def build_report_slow(rows):
report = ""
for row in rows:
report += f"{row}{chr(10)}" # на больших N — квадратичная деградация
return report
def build_report_fast(rows):
parts = [f"{row}{chr(10)}" for row in rows]
return "".join(parts) # линейная сложность
import timeit
rows = list(range(20000))
# timeit.timeit(lambda: build_report_slow(rows), number=5)
# timeit.timeit(lambda: build_report_fast(rows), number=5)
# на реальном прогоне build_report_fast заметно быстрее при росте len(rows)Что использовать на практике
- Практика. Профилировать такую проблему можно через
cProfile/timeitна реальном объёме данных, сравнивая время при росте N: квадратичная деградация видна по тому, что время растёт быстрее, чем линейно с увеличением N. Исправление — копить фрагменты в list и один раз вызвать"".join(parts), что имеет линейную сложность от суммарной длины.
Подводные камни
- Риски. Оптимизируют "на глаз" без замера на реалистичном объёме данных и упускают случаи, когда проблема проявляется только при достаточно больших N (в тестах с маленькими данными деградация не заметна). Также путают эту проблему с более общим советом "всегда используйте join", не объясняя, почему конкретно += в цикле проблематичен именно для больших N.