Как работает механизм конкатенации строк в Python
Что проверяют
Знаете ли вы, что строки неизменяемы и чем это оборачивается в циклах, и можете ли отличить правило языка от оптимизации конкретного интерпретатора. Следом обычно спрашивают, почему join быстрее и что выбрать для форматирования и больших объёмов.
Как отвечать
Строка str неизменяема, поэтому a + b всегда создаёт новый объект и копирует в него оба куска. В цикле s += part каждая итерация копирует всё накопленное, и в общем случае время растёт квадратично.
Дальше нюанс, который отличает сильный ответ. В CPython есть оптимизация: если у левой строки единственная ссылка, интерпретатор пытается расширить её на месте, и на практике такой цикл часто работает почти линейно. Полагаться на это нельзя: PEP 8 прямо просит не рассчитывать на эту особенность, в PyPy и других реализациях её может не быть, а лишняя ссылка на строку отключает оптимизацию и в CPython.
Что выбирать:
"".join(parts): для списка или потока кусков. Метод считает итоговую длину, выделяет память один раз и копирует каждый кусок ровно однажды;- f-строки: для фиксированного числа частей,
f"{name}: {count}". Компилируются в одну операцию сборки строки без цепочки промежуточных склеек и читаются лучше, чем+или%; - литералы рядом:
"abc" "def"склеиваются ещё при компиляции; io.StringIO: когда текст пишется по кускам в разных местах кода;bytearray: для байтов, это изменяемый буфер, в отличие отbytes.
Ещё деталь: + не приводит типы, "id" + 5 даёт TypeError, в отличие от JavaScript.
Где спотыкаются
Утверждают, что += в цикле в CPython всегда квадратичен. Это неточно, и интервьюер может попросить замер. Правильная позиция: сейчас работает быстро по случайности реализации, поэтому пишем через join осознанно.
Передают в join генератор, считая, что так экономят память. join всё равно сначала превращает его в список, а генератор выходит чуть медленнее готового списка.
Забывают, что join не вызывает str() за вас: список чисел даст TypeError, нужен map(str, items).