Каков наиболее эффективный метод конкатенации строк в python?


148

Есть ли эффективный массовый метод конкатенации строк в Python (например, StringBuilder в C # или StringBuffer в Java)? Я нашел следующие методы здесь :

  • Простое объединение с использованием +
  • Использование списка строк и joinметода
  • Использование UserStringиз MutableStringмодуля
  • Использование массива символов и arrayмодуля
  • Использование cStringIOиз StringIOмодуля

Но что вы, эксперты, используете или предлагаете и почему?

[ Связанный вопрос здесь ]


1
Аналогичный вопрос: stackoverflow.com/questions/476772
— Питер Мортенсен

Для объединения известных фрагментов в один, Python 3.6 будет иметь f''строки форматирования, которые будут быстрее любых альтернатив в предыдущих версиях Python.
— Антти Хаапала

Ответы:


127

Вы можете быть заинтересованы в этом: Анекдот оптимизации от Guido. Хотя стоит помнить также, что это старая статья, и она предшествует существованию таких вещей, как ''.join(хотя я думаю, string.joinfieldsчто более или менее то же самое)

В силу этого, arrayмодуль может быть самым быстрым, если вы можете включить в него свою проблему. Но ''.join, вероятно, он достаточно быстр и имеет преимущество в том, что он идиоматичен, и поэтому его легче понять другим программистам на Python.

Наконец, золотое правило оптимизации: не оптимизируйте, если вы не знаете, что нужно, и измеряйте, а не угадывайте.

Вы можете измерить различные методы, используя timeitмодуль. Это может сказать вам, что является самым быстрым, вместо случайных незнакомцев в Интернете, делающих догадки.


1
Желая добавить к вопросу о том, когда оптимизировать: обязательно проверяйте на худшие случаи. Например, я могу увеличить мой пример, чтобы мой текущий код работал с 0,17 секунды до 170 секунд. Ну, я хочу протестировать на больших размерах выборки, так как там меньше вариаций.
— Флиппер

2
«Не оптимизируйте, пока не узнаете, что вам нужно». Если вы просто не используете номинально отличную идиому и можете избежать переделки вашего кода без особых дополнительных усилий.
— jeremyjjbrown

1
Одно место, где вы знаете, что вам нужно, это собеседование (это всегда прекрасное время, чтобы освежить ваше глубокое понимание). К сожалению, я не нашел ни одной современной статьи об этом. (1) Java / C # String все еще так плоха в 2017 году? (2) Как насчет C ++? (3) Теперь расскажите о новейших и лучших в Python фокусировках на тех случаях, когда нам нужно сделать миллионы конкатенаций. Можем ли мы верить, что объединение будет работать за линейное время?
— user1854182

Что значит «достаточно быстро» .join()? Основной вопрос заключается в том, создает ли это a) копию строки для конкатенации (аналогично s = s + 'abc'), для которой требуется время выполнения O (n), или b) просто добавляется к существующей строке, не создавая копию, для которой требуется O (1) ?
— CGFoX

64

''.join(sequenceofstrings) это то, что обычно работает лучше всего - самый простой и быстрый.


3
@mshsayem, в Python последовательность может быть любым перечисляемым объектом, даже функцией.
— Ник Дандулакис

2
Я очень люблю эту ''.join(sequence)идиому. Особенно полезно создавать разделенные запятыми списки: ', '.join([1, 2, 3])выдает строку '1, 2, 3'.
— Эндрю Китон

7
@mshsayem: "".join(chr(x) for x in xrange(65,91))--- в этом случае аргумент для присоединения является итератором, созданным с помощью выражения генератора. Там нет временного списка, который создается.
— Балфа

2
@balpha: и все же версия генератора медленнее, чем версия для понимания списка: C: \ temp> python -mtimeit "'' .join (chr (x) для x в xrange (65,91))" 100000 циклов, лучший из 3: 9.71 usec на цикл C: \ temp> python -mtimeit "'' .join ([chr (x) для x в xrange (65,91)])" 100000 циклов, лучше всего 3: 7.1 usec на цикл
— hughdbrown

1
@hughdbrown, да, когда у вас есть свободная память из списка wazoo (типичный случай), listcomp может быть лучше оптимизирован, чем genexp, часто на 20-30%. Когда в памяти все по-другому - трудно воспроизвести вовремя, хотя! -)
— Алекс Мартелли

58

Python 3.6 изменил игру для конкатенации строк известных компонентов с помощью Literal String Interpolation .

Учитывая тестовый пример из ответа Мкойстинена , имея строки

domain = 'some_really_long_example.com'
lang = 'en'
path = 'some/really/long/path/'

Претенденты

  • f'http://{domain}/{lang}/{path}'- 0,151 мкс

  • 'http://%s/%s/%s' % (domain, lang, path) - 0,321 мкс

  • 'http://' + domain + '/' + lang + '/' + path - 0,356 мкс

  • ''.join(('http://', domain, '/', lang, '/', path))- 0,249 мкс (обратите внимание, что построение кортежа постоянной длины немного быстрее, чем создание списка постоянной длины).

Таким образом, в настоящее время самый короткий и самый красивый код также самый быстрый.

В альфа-версиях Python 3.6 реализация f''строк была самой медленной из возможных - фактически сгенерированный байт-код в значительной степени эквивалентен ''.join()случаю с ненужными вызовами, str.__format__которые без аргументов просто возвращали бы selfбез изменений. Эти недостатки были устранены до финальной версии 3.6.

Скорость можно сравнить с самым быстрым методом для Python 2, который является +конкатенацией на моем компьютере; и это занимает 0,203 мкс с 8-битными строками и 0,259 мкс, если все строки являются Unicode.


38

Это зависит от того, что вы делаете.

После Python 2.5 конкатенация строк с оператором + выполняется довольно быстро. Если вы просто объединяете пару значений, лучше использовать оператор +:

>>> x = timeit.Timer(stmt="'a' + 'b'")
>>> x.timeit()
0.039999961853027344

>>> x = timeit.Timer(stmt="''.join(['a', 'b'])")
>>> x.timeit()
0.76200008392333984

Однако, если вы собираете строку в цикле, вам лучше использовать метод объединения списков:

>>> join_stmt = """
... joined_str = ''
... for i in xrange(100000):
...   joined_str += str(i)
... """
>>> x = timeit.Timer(join_stmt)
>>> x.timeit(100)
13.278000116348267

>>> list_stmt = """
... str_list = []
... for i in xrange(100000):
...   str_list.append(str(i))
... ''.join(str_list)
... """
>>> x = timeit.Timer(list_stmt)
>>> x.timeit(100)
12.401000022888184

... но обратите внимание, что вы должны собрать относительно большое количество строк, прежде чем разница станет заметной.


2
1) В вашем первом измерении, вероятно, на создание списка уходит время. Попробуй с кортежем. 2) CPython работает одинаково хорошо, однако другие реализации Python работают намного хуже с + и + =
— u0b34a0f6ae

22

Согласно ответу Джона Фухи, не оптимизируйте без необходимости, но если вы здесь и задаете этот вопрос, это может быть именно потому, что вам нужно . В моем случае мне нужно было собрать несколько URL из строковых переменных ... быстро. Я заметил, что никто (пока), похоже, не рассматривает метод строкового формата, поэтому я подумал, что попробую это, и, в основном, из-за небольшого интереса, я решил добавить туда оператор строковой интерполяции для лучшего измерения. Честно говоря, я не думал, что ни один из них не будет преобразован в прямую операцию «+» или «.join ()». Но угадайте что? В моей системе Python 2.7.5 оператор строковой интерполяции управляет ими всеми, а string.format () работает хуже всех:

# concatenate_test.py

from __future__ import print_function
import timeit

domain = 'some_really_long_example.com'
lang = 'en'
path = 'some/really/long/path/'
iterations = 1000000

def meth_plus():
    '''Using + operator'''
    return 'http://' + domain + '/' + lang + '/' + path

def meth_join():
    '''Using ''.join()'''
    return ''.join(['http://', domain, '/', lang, '/', path])

def meth_form():
    '''Using string.format'''
    return 'http://{0}/{1}/{2}'.format(domain, lang, path)

def meth_intp():
    '''Using string interpolation'''
    return 'http://%s/%s/%s' % (domain, lang, path)

plus = timeit.Timer(stmt="meth_plus()", setup="from __main__ import meth_plus")
join = timeit.Timer(stmt="meth_join()", setup="from __main__ import meth_join")
form = timeit.Timer(stmt="meth_form()", setup="from __main__ import meth_form")
intp = timeit.Timer(stmt="meth_intp()", setup="from __main__ import meth_intp")

plus.val = plus.timeit(iterations)
join.val = join.timeit(iterations)
form.val = form.timeit(iterations)
intp.val = intp.timeit(iterations)

min_val = min([plus.val, join.val, form.val, intp.val])

print('plus %0.12f (%0.2f%% as fast)' % (plus.val, (100 * min_val / plus.val), ))
print('join %0.12f (%0.2f%% as fast)' % (join.val, (100 * min_val / join.val), ))
print('form %0.12f (%0.2f%% as fast)' % (form.val, (100 * min_val / form.val), ))
print('intp %0.12f (%0.2f%% as fast)' % (intp.val, (100 * min_val / intp.val), ))

Результаты:

# python2.7 concatenate_test.py
plus 0.360787868500 (90.81% as fast)
join 0.452811956406 (72.36% as fast)
form 0.502608060837 (65.19% as fast)
intp 0.327636957169 (100.00% as fast)

Если я использую более короткий домен и более короткий путь, интерполяция все еще выигрывает. Разница более выражена, однако, с более длинными строками.

Теперь, когда у меня был хороший тестовый скрипт, я также тестировал под Python 2.6, 3.3 и 3.4, вот результаты. В Python 2.6 оператор плюс - самый быстрый! На Python 3 присоединение выигрывает. Примечание: эти тесты очень повторяются в моей системе. Таким образом, «плюс» всегда быстрее в 2.6, «intp» всегда быстрее в 2.7 и «соединение» всегда быстрее в Python 3.x.

# python2.6 concatenate_test.py
plus 0.338213920593 (100.00% as fast)
join 0.427221059799 (79.17% as fast)
form 0.515371084213 (65.63% as fast)
intp 0.378169059753 (89.43% as fast)

# python3.3 concatenate_test.py
plus 0.409130576998 (89.20% as fast)
join 0.364938726001 (100.00% as fast)
form 0.621366866995 (58.73% as fast)
intp 0.419064424001 (87.08% as fast)

# python3.4 concatenate_test.py
plus 0.481188605998 (85.14% as fast)
join 0.409673971997 (100.00% as fast)
form 0.652010936996 (62.83% as fast)
intp 0.460400978001 (88.98% as fast)

# python3.5 concatenate_test.py
plus 0.417167026084 (93.47% as fast)
join 0.389929617057 (100.00% as fast)
form 0.595661019906 (65.46% as fast)
intp 0.404455224983 (96.41% as fast)

Урок выучен:

  • Иногда мои предположения совершенно неверны.
  • Тест по системе env. вы будете работать в производстве.
  • Строковая интерполяция еще не умерла!

ТЛ; др:

  • Если вы используете 2.6, используйте оператор +.
  • если вы используете 2.7, используйте оператор «%».
  • если вы используете 3.x, используйте '' .join ().

2
Примечание: интерполяция литеральных строк еще быстрее для 3.6+:f'http://{domain}/{lang}/{path}'
— TemporalWolf

1
Кроме того , .format()есть три формы, в порядке от быстрого к медленному: "{}".format(x), "{0}".format(x),"{x}".format(x=x)
— TemporalWolf

Реальный урок: когда ваша проблемная область мала, например, составляя короткие строки, метод чаще всего не имеет значения. И даже когда это имеет значение, например, вы действительно строите миллион строк, накладные расходы часто имеют большее значение. Это типичный симптом беспокойства о неправильной проблеме. Только тогда, когда накладные расходы незначительны, например, при создании всей книги в виде строки, разница в методах начинает иметь значение.
— Хуэй Чжоу

7

это в значительной степени зависит от относительных размеров новой строки после каждой новой конкатенации. С помощью +оператора для каждой конкатенации создается новая строка. Если промежуточные строки относительно долго, +становится все более и более медленно , потому что новый посредник строка хранится.

Рассмотрим этот случай:

from time import time
stri=''
a='aagsdfghfhdyjddtyjdhmfghmfgsdgsdfgsdfsdfsdfsdfsdfsdfddsksarigqeirnvgsdfsdgfsdfgfg'
l=[]
#case 1
t=time()
for i in range(1000):
    stri=stri+a+repr(i)
print time()-t

#case 2
t=time()
for i in xrange(1000):
    l.append(a+repr(i))
z=''.join(l)
print time()-t

#case 3
t=time()
for i in range(1000):
    stri=stri+repr(i)
print time()-t

#case 4
t=time()
for i in xrange(1000):
    l.append(repr(i))
z=''.join(l)
print time()-t

Полученные результаты

1 0,00493192672729

2 0.000509023666382

3 0.00042200088501

4 0.000482797622681

В случае 1 и 2 мы добавляем большую строку, а join () работает примерно в 10 раз быстрее. В случаях 3 и 4 мы добавляем небольшую строку, и «+» работает немного быстрее


3

Я столкнулся с ситуацией, когда мне нужно было добавить строку неизвестного размера. Это результаты тестов (python 2.7.3):

$ python -m timeit -s 's=""' 's+="a"'
10000000 loops, best of 3: 0.176 usec per loop
$ python -m timeit -s 's=[]' 's.append("a")'
10000000 loops, best of 3: 0.196 usec per loop
$ python -m timeit -s 's=""' 's="".join((s,"a"))'
100000 loops, best of 3: 16.9 usec per loop
$ python -m timeit -s 's=""' 's="%s%s"%(s,"a")'
100000 loops, best of 3: 19.4 usec per loop

Это, кажется, показывает, что «+ =» является самым быстрым. Результаты по ссылке Skymind немного устарели.

(Я понимаю, что второй пример не завершен, к окончательному списку нужно будет присоединиться. Однако это показывает, что простая подготовка списка занимает больше времени, чем строка concat.)


Я получаю саб 1 раз за 3-й и 4-й тесты. Почему вы получаете такие высокие времена? pastebin.com/qabNMCHS
— bad_keypoints

@ronnieaka: он получает суб 1 раз за все тесты. Он получает> 1 мкс для 3-го и 4-го, что вы не сделали. Я также получаю более медленное время на этих тестах (на Python 2.7.5, Linux). Может быть процессор, версия, флаги сборки, кто знает.
— Танатос

Эти результаты теста бесполезны. В частности, в первом случае, который не выполняет конкатенацию строк, а просто возвращает второе значение строки без изменений.
— Антти Хаапала

3

Через год давайте проверим ответ mkoistinen с помощью python 3.4.3:

  • плюс 0,963564149000 (95,83% быстрее)
  • присоединиться 0.923408469000 (100,00% быстрее)
  • Форма 1.501130934000 (скорость 61,51%)
  • int 1.019677452000 (90,56% быстрее)

Ничего не изменилось. Регистрация по-прежнему самый быстрый метод. Поскольку intp является, пожалуй, лучшим выбором с точки зрения читабельности, вы, возможно, захотите использовать intp.


1
Возможно, это может быть дополнением к ответу mkoistinen, так как для него не хватает полного ответа (или, по крайней мере, добавьте код, который вы используете).
— Триларион

1

Вдохновленный тестами @ JasonBaker, вот простой пример, сравнивающий 10 "abcdefghijklmnopqrstuvxyz"строк и показывающий, что .join()он быстрее; даже с этим крошечным увеличением переменных:

сцепление

>>> x = timeit.Timer(stmt='"abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz"')
>>> x.timeit()
0.9828147209324385

Присоединиться

>>> x = timeit.Timer(stmt='"".join(["abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz"])')
>>> x.timeit()
0.6114138159765048

Посмотрите на принятый ответ (прокрутите вниз) на этот вопрос: stackoverflow.com/questions/1349311/…
— mshsayem

1

Для небольшого набора из коротких строк (то есть 2 или 3 строк не больше , чем несколько символов), а также по - прежнему намного быстрее. Используя замечательный скрипт mkoistinen в Python 2 и 3:

plus 2.679107467004 (100.00% as fast)
join 3.653773699996 (73.32% as fast)
form 6.594011374000 (40.63% as fast)
intp 4.568015249999 (58.65% as fast)

Поэтому, когда ваш код выполняет огромное количество отдельных небольших объединений, плюс является предпочтительным способом, если скорость имеет решающее значение.


1

Вероятно, «новые f-строки в Python 3.6» - наиболее эффективный способ объединения строк.

Используя% s

>>> timeit.timeit("""name = "Some"
... age = 100
... '%s is %s.' % (name, age)""", number = 10000)
0.0029734770068898797

Использование .format

>>> timeit.timeit("""name = "Some"
... age = 100
... '{} is {}.'.format(name, age)""", number = 10000)
0.004015227983472869

Используя f

>>> timeit.timeit("""name = "Some"
... age = 100
... f'{name} is {age}.'""", number = 10000)
0.0019175919878762215

Источник: https://realpython.com/python-f-strings/

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.