Как объединить строки в Python: использование .join() вместо +
Понятное — не всегда значит лучшее.


: официальный арт к игре «ведьмак 3» / cd projekt red
Python ценят за его интуитивную понятность. Так, строки можно объединить простым плюсом:
s1 = 'Я люблю '
s2 = 'программировать на Python'
s1 + s2
>>> 'Я люблю программировать на Python'
На первый взгляд странно, что многие профессиональные Python-разработчики не пользуются этим очевидным и, казалось бы, удобным методом, а вместо него применяют .join().
Почему — давайте разбираться.

Что ещё за .join() такой?
Возьмём те же строки s1 и s2 и попробуем повторить предыдущий результат с помощью .join():
''.join([s1, s2])
>>> 'Я люблю программировать на Python'
Первая мысль: что здесь происходит? Зачем пустая строка? Почему строки s1 и s2 засунуты в список? Почему всё так не наглядно? А это точно Python?
Итак, по порядку:
- Пустая строка ' ' — это строка-соединитель: то, что будет между строками s1 и s2. В нашем случае она пустая, но это не обязательно — там может быть, например, пробел или знак препинания.
- Строки s1 и s2 составлены в список [s1, s2] потому, что join() принимает только один аргумент. Как правило, это итерируемый объект — список, кортеж или словарь из строк. В случае словаря функция join() объединит его ключи.
''.join((s1, s2)) # кортеж
>>> 'Я люблю программировать на Python'
''.join({s1:'a', s2:'b'}) # словарь с s1 и s2 в качестве ключей
>>> 'Я люблю программировать на Python'
''.join({'a':s1, 'b':s2}) # словарь с s1, s2 в качестве значений
>>> 'ab'
Вроде бы с join() разобрались. Но главный вопрос остался открытым: «Зачем его использовать?»
Когда строк много
Представим, что вы получили много строк в одном списке и теперь надо объединить их в одну. Попробуем решить задачу с помощью плюса:
strings = ['Жизнь', 'слишком', 'коротка,', 'программируй', 'на', 'Python']
def join_strs(strs):
result = ''
for s in strs:
result += ' ' + s # а вот и плюс
return result[1:]
join_strs(strings)
>>> 'Жизнь слишком коротка, программируй на Python'
В теле функции join_strs() мы создаём пустую строку result. Далее с помощью цикла for бежим по полученному списку из строк и с каждой итерацией добавляем к строке result пробел и строку из списка — с помощью плюса, разумеется.
В конце return возвращает строку result без первого элемента — там стоит ненужный нам пробел, добавившийся при первой итерации.
Итак, чтобы использовать ‘+’ для объединения строк из списка:
- нужен цикл for,
- нужно помнить о лишнем пробеле.
Посмотрим, что может предложить join():
def join_strs_better(strs):
return ' '.join(strs)
join_strs_better(strings)
>>> 'Жизнь слишком коротка, программируй на Python'
Ого! Выглядит неплохо: никаких циклов и не надо беспокоиться о пробеле — функция сама обо всём позаботилась, причём в одну строчку.
В принципе, этого уже достаточно, чтобы забыть о ‘+’ — во всяком случае, для большого количества строк. Но есть ещё одна — возможно, главная — причина.
Производительность
Встроенная Python-библиотека timeit предназначена для измерения времени исполнения кода небольшого размера (скриптов, или сниппетов). Как раз наш случай.
Передадим код из примеров выше в качестве аргумента в функцию timeit.timeit(), исполним его миллион раз для обеих наших функций и сравним время:
import timeit
timeit.timeit(
'''
strings = ['Жизнь', 'слишком', 'коротка,', 'программируй', 'на', 'Python']
def join_strs(strs):
result = ''
for s in strs:
result += ' ' + s
return result[1:]
join_strs(strings)
'''
, number=1000000)
>>> 1.1924651000001631
timeit.timeit(
'''
strings = ['Жизнь', 'слишком', 'коротка,', 'программируй', 'на', 'Python']
def join_strs_better(strs):
return ' '.join(strs)
join_strs_better(strings)
'''
, number=1000000)
>>> 0.39118409999991854
Объединение шести строк в одну с помощью плюса в три раза медленнее, чем объединение этих же строк с помощью функции join().
С увеличением количества объединяемых строк эта разница будет расти — попробуйте проверить это самостоятельно.
В чём причина?
Как работают + и join()
Приблизительная схема работы цикла for с плюсом внутри выглядит примерно так.

- На каждой итерации цикла из списка вынимается строка.
- Интерпретатор выполняет команду result += ' ' + s, сначала запрашивая память для пробела ' '.
- Затем он запрашивает память для строки s.
- Так происходит шесть раз, и в каждой итерации есть два запроса в память — для пробела и для строки, итого 12.
А что происходит, когда мы объединяем эти же строки с помощью .join()?

- Интерпретатор подсчитывает количество строк в списке: 6.
- Подсчитывает количество нужных для объединения пробелов: 5.
- Обращается в память сразу за одиннадцатью ячейками.
- Формирует строку, выдаёт результат.
Как видим, всё происходит гораздо компактнее. Меньшее количество обращений к памяти — основная причина более высокой скорости работы функции join() по сравнению с циклом for и +.
Подытожим
Дружелюбие синтаксиса Python к начинающим — это прекрасно, без всяких преувеличений. Но если программист не хочет быть начинающим всю свою жизнь, то придётся постоянно осваивать более мощные и часто менее очевидные инструменты, подходы и приёмы.
На курсах «Профессии Python-разработчик» вы не только познакомитесь именно с такими инструментами, но и выработаете привычку к постоянному их поиску и совершенствованию. В этом, возможно, и заключается суть программирования. Приходите, и да пребудут с вами Дзен и Дух Пайтона!