Какой самый эффективный метод конкатенации строк в Python? - PullRequest
123 голосов
/ 22 августа 2009

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

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

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

[ Смежный вопрос здесь ]

Ответы [ 11 ]

113 голосов
/ 23 августа 2009

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

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

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

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

56 голосов
/ 22 августа 2009

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

38 голосов
/ 14 июля 2016

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

С учетом теста из ответа Мкойстинена , имеющего строки

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.

36 голосов
/ 23 августа 2009

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

После 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

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

14 голосов
/ 13 июля 2014

Согласно ответу Джона Фухи, не оптимизируйте без необходимости, но если вы здесь и задаете этот вопрос, это может быть именно потому, что вам нужно . В моем случае мне нужно было собрать несколько 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 ().
4 голосов
/ 12 марта 2014

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

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

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 мы добавляем небольшую строку, и «+» работает немного быстрее

2 голосов
/ 29 ноября 2015

Год спустя, давайте проверим ответ mkoistinen с python 3.4.3:

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

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

2 голосов
/ 07 сентября 2012

Я столкнулся с ситуацией, когда мне понадобилась добавляемая строка неизвестного размера. Это результаты тестов (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.)

0 голосов
/ 22 мая 2018

Вероятно, «новые 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/

0 голосов
/ 02 февраля 2017

Для небольшого набора из коротких строк (то есть 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)

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...