Строка "join" Python быстрее (?), Чем "+", но что здесь не так?

Я спросил наиболее эффективный метод для массовой динамической конкатенации строк в более раннем сообщении, и мне предложили использовать метод join, лучший, самый простой и быстрый способ сделать это (поскольку все сказали, что). Но пока я играл со строковыми конкатенациями, я нашел некоторые странные (?) Результаты. Я уверен, что что-то происходит, но я не могу понять. Вот что я сделал:

Я определил эти функции:

import timeit
def x():
    s=[]
    for i in range(100):
        # Other codes here...
        s.append("abcdefg"[i%7])
    return ''.join(s)

def y():
    s=''
    for i in range(100):
        # Other codes here...
        s+="abcdefg"[i%7]
    return s

def z():
    s=''
    for i in range(100):
        # Other codes here...
        s=s+"abcdefg"[i%7]
    return s

def p():
    s=[]
    for i in range(100):
        # Other codes here...
        s+="abcdefg"[i%7]
    return ''.join(s)

def q():
    s=[]
    for i in range(100):
        # Other codes here...
        s = s + ["abcdefg"[i%7]]
    return ''.join(s)

Я пытался сохранить другие вещи (кроме конкатенации) почти одинаково во всех функциях. Затем я протестировал со следующими результатами с комментариями (используя Python 3.1.1 IDLE на 32-битной машине Windows):

timeit.timeit(x) # 31.54912480500002
timeit.timeit(y) # 23.533029429999942 
timeit.timeit(z) # 22.116181330000018
timeit.timeit(p) # 37.718607439999914
timeit.timeit(q) # 108.60377576499991

Это означает, что он показывает, что strng = strng + dyn_strng является самым быстрым. Хотя разница во времени не такая значительная (кроме последней), но я хочу знать, почему это происходит. Это потому, что я использую Python 3.1.1 и который обеспечивает "+" как наиболее эффективный? Должен ли я использовать "+" в качестве альтернативы для присоединения? Или я сделал что-то очень глупое? Или что? Пожалуйста, объясните четко.

Ответ 1

Я выяснил ответ из ответов, представленных экспертами. В зависимости от них (насколько я видел) конкатенация строк Python (и временные измерения):

  • Количество конкатенаций
  • Средняя длина строк
  • Число вызовов функций

Я создал новый код, который связывает их. Спасибо Питеру S Магнуссону, sepp2k, hughdbrown, Дэвиду Волеверу и другим за указание важных моментов, которые я пропустил ранее. Кроме того, в этом коде я мог что-то пропустить. Поэтому я высоко ценю любые ответы, указывающие на наши ошибки, предложения, критику и т.д. В конце концов, я здесь для обучения. Вот мой новый код:

from timeit import timeit

noc = 100
tocat = "a"
def f_call():
    pass

def loop_only():
    for i in range(noc):
        pass

def concat_method():
    s = ''
    for i in range(noc):
        s = s + tocat

def list_append():
    s=[]
    for i in range(noc):
        s.append(tocat)
    ''.join(s)

def list_append_opt():
    s = []
    zap = s.append
    for i in range(noc):
        zap(tocat)
    ''.join(s)

def list_comp():
    ''.join(tocat for i in range(noc))

def concat_method_buildup():
    s=''

def list_append_buildup():
    s=[]

def list_append_opt_buildup():
    s=[]
    zap = s.append

def function_time(f):
    return timeit(f,number=1000)*1000

f_callt = function_time(f_call)

def measure(ftuple,n,tc):
    global noc,tocat
    noc = n
    tocat = tc
    loopt = function_time(loop_only) - f_callt
    buildup_time = function_time(ftuple[1]) -f_callt if ftuple[1] else 0
    total_time = function_time(ftuple[0])
    return total_time, total_time - f_callt - buildup_time - loopt*ftuple[2]

functions ={'Concat Method\t\t':(concat_method,concat_method_buildup,True),
            'List append\t\t\t':(list_append,list_append_buildup,True),
            'Optimized list append':(list_append_opt,list_append_opt_buildup,True),
            'List comp\t\t\t':(list_comp,0,False)}

for i in range(5):
    print("\n\n%d concatenation\t\t\t\t10'a'\t\t\t\t 100'a'\t\t\t1000'a'"%10**i)
    print('-'*80)
    for (f,ft) in functions.items():
        print(f,"\t|",end="\t")
        for j in range(3):
            t = measure(ft,10**i,'a'*10**j)
            print("%.3f %.3f |" % t,end="\t")
        print()

И вот что я получил. [В столбце времени отображаются два раза (масштабированные): первое - это время выполнения полной функции, а второе время - фактическое (?) Время конкатенации. Я вычитал время вызова функции, время нарастания функции (время инициализации) и время итерации. Здесь я рассматриваю случай, когда он не может быть выполнен без цикла (например, больше внутри).]

1 concatenation                 1'a'                  10'a'               100'a'
-------------------     ----------------------  -------------------  ----------------
List comp               |   2.310 2.168       |  2.298 2.156       |  2.304 2.162
Optimized list append   |   1.069 0.439       |  1.098 0.456       |  1.071 0.413
Concat Method           |   0.552 0.034       |  0.541 0.025       |  0.565 0.048
List append             |   1.099 0.557       |  1.099 0.552       |  1.094 0.552


10 concatenations                1'a'                  10'a'               100'a'
-------------------     ----------------------  -------------------  ----------------
List comp               |   3.366 3.224       |  3.473 3.331       |  4.058 3.916
Optimized list append   |   2.778 2.003       |  2.956 2.186       |  3.417 2.639
Concat Method           |   1.602 0.943       |  1.910 1.259       |  3.381 2.724
List append             |   3.290 2.612       |  3.378 2.699       |  3.959 3.282


100 concatenations               1'a'                  10'a'               100'a'
-------------------     ----------------------  -------------------  ----------------
List comp               |   15.900 15.758     |  17.086 16.944     |  20.260 20.118
Optimized list append   |   15.178 12.585     |  16.203 13.527     |  19.336 16.703
Concat Method           |   10.937 8.482      |  25.731 23.263     |  29.390 26.934
List append             |   20.515 18.031     |  21.599 19.115     |  24.487 22.003


1000 concatenations               1'a'                  10'a'               100'a'
-------------------     ----------------------  -------------------  ----------------
List comp               |   134.507 134.365   |  143.913 143.771   |  201.062 200.920
Optimized list append   |   112.018 77.525    |  121.487 87.419    |  151.063 117.059
Concat Method           |   214.329 180.093   |  290.380 256.515   |  324.572 290.720
List append             |   167.625 133.619   |  176.241 142.267   |  205.259 171.313


10000 concatenations              1'a'                  10'a'               100'a'
-------------------     ----------------------  -------------------  ----------------
List comp               |   1309.702 1309.560 |  1404.191 1404.049 |  2912.483 2912.341
Optimized list append   |   1042.271 668.696  |  1134.404 761.036  |  2628.882 2255.804
Concat Method           |   2310.204 1941.096 |  2923.805 2550.803 |  STUCK    STUCK
List append             |   1624.795 1251.589 |  1717.501 1345.137 |  3182.347 2809.233

Подводя итог всем этим, я принял решение для меня:

  • Если у вас есть список строк, метод string 'join' является лучшим и быстрее всего.
  • Если вы можете использовать понимание списка, что и самый простой и быстрый.
  • Если вам требуется от 1 до 10 конкатенаций (средний) с длиной от 1 до 100, список append, '+' обе принимают одинаковое (почти, обратите внимание, что время масштабируется).
  • Оптимизированный список приложений кажется очень хорошо в большинстве ситуаций.
  • Когда #concatenation или длина строки увеличивается, '+' начинает принимать значительно больше   и больше времени. Обратите внимание, что для 10000 конкатенаций с 100'a мой компьютер застрял!
  • Если вы используете список append и 'join' всегда, вы все время в безопасности (указано Алексом   Мартелли).
  • Но в какой-то ситуации скажите, где вы необходимо вводить данные пользователя и распечатывать "Привет, мир пользователя!", Проще всего использовать "+". Я думаю, что создание списка и присоединитесь к этому случаю, например x = input ( "Введите имя пользователя:" ), а затем x.join([ "Hello", "world!" ]) уродливее, чем "Hello% s world!" % x или "Hello" + x + "мир"
  • Python 3.1 улучшил конкатенация. Но в некоторая реализация   как Jython, "+" менее эффективен.
  • Преждевременная оптимизация - это корень от всего зла (говорят эксперты). Наиболее времени   вам не нужна оптимизация. Итак, не теряйте время в аспирации для оптимизации   (если вы не пишете большой или вычислительный проект, где каждый микро/миллисекунда   имеет значение.
  • Используйте эту информацию и напишите  как вам нравится  обстоятельства,    рассмотрение.
  • Если вам действительно нужна оптимизация,  используйте профилировщик, найдите  узких мест и    оптимизируйте их.

Наконец, я пытаюсь изучить python более глубоко. Поэтому нет ничего необычного в том, что в моих наблюдениях будут ошибки (ошибки). Итак, прокомментируйте это и предложите мне, если я ошибаюсь. Спасибо всем за участие.

Ответ 2

Некоторые из нас, комментирующие Python, я считаю, в основном, Риго и Хеттингер, ушли с пути (по пути к 2.5, я полагаю), чтобы оптимизировать некоторые частные случаи alas-far-too-common-common s += something , утверждая, что было доказано, что новички никогда не будут обмануты тем, что ''.join - правильный путь, и ужасная медлительность += может дать Python плохое имя. Другие из нас были не такими горячими, потому что они просто не могли оптимизировать каждое событие (или даже большинство из них) до достойной производительности; но мы не чувствовали себя достаточно горячо в этом вопросе, чтобы попытаться активно блокировать их.

Я считаю, что эта нить доказывает, что мы должны были более решительно выступать против них. Как и сейчас, они оптимизировали += в определенном трудно прогнозируемом подмножестве случаев, где оно может быть, возможно, на 20% быстрее для конкретных глупых случаев, чем надлежащим образом (что еще остается ''.join) - просто идеальный способ привлечь новичков к преследованию этих неулокальных 20-процентных выигрышей, используя неправильную идиому... из-за того, что время от времени и из их POV из-за сильного удара с потерей производительности на 200% (или более), так как нелинейное поведение все еще скрывается там, за пределами углов, которые Хеттингер и Риго примасливали и клали цветы;-) - тот, который ВОПРОСЫ, которые БУДУТ сделать их несчастными. Это противоречит зерну Python "в идеале только один очевидный способ сделать это", и мне кажется, что мы вместе коллекционировали ловушку для новичков - лучший вид тоже... те, кто не просто принимает что им говорят их "лучшие", но любознательно идут, спрашивают и исследуют.

Хорошо, я сдаюсь. OP, @mshsayem, продолжайте использовать + = всюду, наслаждайтесь своими неуместными 20% -ыми ускорениями в тривиальных, крошечных, нерелевантных случаях, и вам лучше наслаждаться ими по рукоятке - потому что однажды, когда вы не увидите этого прибывая, на ВАЖНУЮ, БОЛЬШУЮ операцию, вы будете поражены приземлением в придорожном прицепном грузовике с 200% -ным замедлением (если только вам не повезло и это 2000% -ый;-). Просто помните: если вы когда-нибудь почувствуете, что "Python ужасно медленный", ПОМНИТЕ, скорее всего, одна из ваших любимых петель += оборачивается и кусает руку, которая ее подает.

Для остальных из нас - тех, кто понимает, что значит сказать Мы должны забыть о небольшой эффективности, скажем, около 97% времени, я буду сердечно рекомендовать ''.join, поэтому мы все можем спать во всем спокойствии и ЗНАЕМ, что мы не будем поражены сверхлинейным замедлением, когда мы меньше всего ожидаем, и меньше всего вы можете позволить себе. Но для вас, Армин Риго и Раймонд Хеттингер (последние два, мои дорогие личные друзья, BTW, а не только совлокальные участники;-) - пусть ваш += будет плавным, а ваш большой-O никогда не будет хуже, чем N! -)

Итак, для остальных из нас здесь более значимый и интересный набор измерений:

$ python -mtimeit -s'r=[str(x)*99 for x in xrange(100,1000)]' 's="".join(r)'
1000 loops, best of 3: 319 usec per loop

900 строк по 297 символов каждый, присоединение к списку напрямую, конечно, самое быстрое, но OP боится, что ему придется делать добавления до этого. Но:

$ python -mtimeit -s'r=[str(x)*99 for x in xrange(100,1000)]' 's=""' 'for x in r: s+=x'
1000 loops, best of 3: 779 usec per loop
$ python -mtimeit -s'r=[str(x)*99 for x in xrange(100,1000)]' 'z=[]' 'for x in r: z.append(x)' '"".join(z)'
1000 loops, best of 3: 538 usec per loop

... с полуважным объемом данных (всего лишь немногим 100 KB - взяв измеряемую часть миллисекунды каждый), даже обычный старый добрый .append имеет более высокий уровень. Кроме того, это очевидно и тривиально легко оптимизировать:

$ python -mtimeit -s'r=[str(x)*99 for x in xrange(100,1000)]' 'z=[]; zap=z.append' 'for x in r: zap(x)' '"".join(z)'
1000 loops, best of 3: 438 usec per loop

брить еще десятки миллисекунд по среднему времени цикла. Все (по крайней мере, все, кто полностью одержим изобилием производительности), очевидно, знают, что ПОДНИМАНИЕ (извлечение из внутреннего цикла повторяющихся вычислений, которое иначе выполнялось бы снова и снова) является решающим методом оптимизации - Python не поднимает от вашего имени, поэтому вам нужно делать свой собственный подъем в тех редких случаях, когда каждый микросекунда имеет значение.

Ответ 3

Что касается того, почему q намного медленнее: когда вы говорите

l += "a"

вы добавляете строку "a" в конец l, но когда вы говорите

l = l + ["a"]

вы создаете новый список с содержимым l и ["a"], а затем переназначаете результаты обратно на l. Таким образом, постоянно создаются новые списки.

Ответ 4

Я предполагаю, что x() медленнее, потому что вы сначала создаете массив, а затем присоединяете его. Таким образом, вы не только измеряете время соединения, но и время, которое вы берете для создания массива.

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

Ответ 5

Этот вопрос действительно о том, что стоит. Мы немного поиграем и проиграем здесь, вычитая результаты в подобных случаях. Вы можете решить для себя, если это действительный метод. Вот некоторые основные тестовые примеры:

import timeit
def append_to_list_with_join():
    s=[]
    for i in xrange(100):
        s.append("abcdefg"[i%7])
    return ''.join(s)

def append_to_list_with_join_opt():
    s=[]
    x = s.append
    for i in xrange(100):
        x("abcdefg"[i%7])
    return ''.join(s)

def plus_equals_string():
    s=''
    for i in xrange(100):
        s+="abcdefg"[i%7]
    return s

def plus_assign_string():
    s=''
    for i in xrange(100):
        s=s+"abcdefg"[i%7]
    return s

def list_comp_join():
    return ''.join(["abcdefg"[i%7] for i in xrange(100)])

def list_comp():
    return ["abcdefg"[i%7] for i in xrange(100)]

def empty_loop():
    for i in xrange(100):
        pass

def loop_mod():
    for i in xrange(100):
        a = "abcdefg"[i%7]

def fast_list_join():
    return "".join(["0"] * 100)

for f in [append_to_list_with_join, append_to_list_with_join_opt, plus_equals_string,plus_assign_string,list_comp_join, list_comp, empty_loop,loop_mod, fast_list_join]:
    print f.func_name, timeit.timeit(f)

И вот что они стоят:

append_to_list_with_join 25.4540209021
append_to_list_with_join_opt 19.9999782794
plus_equals_string 16.7842428996
plus_assign_string 14.8312124167
list_comp_join 16.329590353
list_comp 14.6934344309
empty_loop 2.3819276612
loop_mod 10.1424356308
fast_list_join 2.58149394686

Во-первых, у многих вещей есть неожиданные затраты на python. append_to_list_with_join versus append_to_list_with_join_opt показывает, что даже поиск метода на объекте имеет незначительную стоимость. В этом случае поиск s.append составляет одну четверть времени.

Далее, list_comp_join и list_comp показывают, что join() довольно быстро: он занимает около 1,7 или только 10% от времени list_comp_join.

loop_mod показывает, что большая часть этого теста фактически заключается в настройке данных, независимо от того, какой метод построения строки используется. По умозаключению время, затраченное на "string = string +", "string + =" и понимание списка:

plus_equals_string = 16.78 - 10.14 = 6.64
plus_assign_string = 14.83 - 10.14 = 4.69
list_comp = 14.69 - 10.14 = 4.55

Итак, что касается вопроса OP, join() выполняется быстро, но время создания базового списка, будь то с примитивами списков или с пониманием списка, сопоставимо с созданием строки со строковыми примитивами. Если у вас уже есть список, преобразуйте его в строку с join() - она ​​будет быстрой.

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

Наконец, возьмем три из ближайших функций OP: в чем разница между x, p и q? Немного упростим:

import timeit
def x():
    s=[]
    for i in range(100):
        s.append("c")

def p():
    s=[]
    for i in range(100):
        s += "c"

def q():
    s=[]
    for i in range(100):
        s = s + ["c"]

for f in [x,p,q]:
    print f.func_name, timeit.timeit(f)

Вот результаты:

x 16.0757342064
p 87.1533697719
q 85.0999698984

И вот разборка:

>>> import dis
>>> dis.dis(x)
  2           0 BUILD_LIST               0
              3 STORE_FAST               0 (s)

  3           6 SETUP_LOOP              33 (to 42)
              9 LOAD_GLOBAL              0 (range)
             12 LOAD_CONST               1 (100)
             15 CALL_FUNCTION            1
             18 GET_ITER
        >>   19 FOR_ITER                19 (to 41)
             22 STORE_FAST               1 (i)

  4          25 LOAD_FAST                0 (s)
             28 LOAD_ATTR                1 (append)
             31 LOAD_CONST               2 ('c')
             34 CALL_FUNCTION            1
             37 POP_TOP
             38 JUMP_ABSOLUTE           19
        >>   41 POP_BLOCK
        >>   42 LOAD_CONST               0 (None)
             45 RETURN_VALUE
>>> dis.dis(p)
  2           0 BUILD_LIST               0
              3 STORE_FAST               0 (s)

  3           6 SETUP_LOOP              30 (to 39)
              9 LOAD_GLOBAL              0 (range)
             12 LOAD_CONST               1 (100)
             15 CALL_FUNCTION            1
             18 GET_ITER
        >>   19 FOR_ITER                16 (to 38)
             22 STORE_FAST               1 (i)

  4          25 LOAD_FAST                0 (s)
             28 LOAD_CONST               2 ('c')
             31 INPLACE_ADD
             32 STORE_FAST               0 (s)
             35 JUMP_ABSOLUTE           19
        >>   38 POP_BLOCK
        >>   39 LOAD_CONST               0 (None)
             42 RETURN_VALUE
>>> dis.dis(q)
  2           0 BUILD_LIST               0
              3 STORE_FAST               0 (s)

  3           6 SETUP_LOOP              33 (to 42)
              9 LOAD_GLOBAL              0 (range)
             12 LOAD_CONST               1 (100)
             15 CALL_FUNCTION            1
             18 GET_ITER
        >>   19 FOR_ITER                19 (to 41)
             22 STORE_FAST               1 (i)

  4          25 LOAD_FAST                0 (s)
             28 LOAD_CONST               2 ('c')
             31 BUILD_LIST               1
             34 BINARY_ADD
             35 STORE_FAST               0 (s)
             38 JUMP_ABSOLUTE           19
        >>   41 POP_BLOCK
        >>   42 LOAD_CONST               0 (None)
             45 RETURN_VALUE

Петли почти идентичны. Сравнение составляет CALL_FUNCTION + POP_TOP против INPLACE_ADD + STORE_FAST и BUILD_LIST + BINARY_ADD + STORE_FAST. Тем не менее, я не могу дать более низкоуровневое объяснение, чем это - я просто не могу найти затраты на байт-коды python в Сети. Тем не менее, вам может показаться, что вы смотрите на модуль Doug Hellmann Python Module of the Week на dis.

Ответ 6

Есть разница между + = и + со строками - если нет других ссылок на "x", x + = y может просто присоединяться к x, вместо того, чтобы брать копию строки для добавления к которой это то же преимущество, которое вы получаете от использования ".join().

Основное преимущество ".join() over + или + = заключается в том, что join() всегда должен давать линейную производительность, а во многих случаях +/+ = дает квадратичную производительность (т.е. когда вы удваиваете количество текст, вы в четыре раза увеличиваете время). Но это будет иметь значение только при большом количестве текста, а не только в 100 байт, и я думаю, что он не будет запущен, если у вас есть только одна ссылка на строку, которую вы добавляете.

Подробнее:

Ваша лучшая производительность для конкатенации строк - это просмотр каждого символа в последней строке один раз. "".join() делает это естественно - у него есть вся необходимая информация с самого начала.

Однако a + = b может работать двумя способами: он может либо просто добавить "b" к существующей строке, и в этом случае ему нужно только посмотреть на символы в "b", или может потребоваться просмотреть символы в "a" тоже.

В C, strcat() всегда смотрит на все символы в обеих строках, поэтому он работает плохо всегда. Однако в Python длина строки сохраняется, поэтому строка может быть расширена до тех пор, пока она не упоминается в другом месте, и вы получаете хорошую производительность, только копируя символы в "b". Если он упоминается в другом месте, python сначала сделает копию "a", а затем добавит "b" в конец, что даст вам плохую производительность. Если вы добавляете пять строк таким образом, ваше время будет:

ab = a+b       # Time is a + b
abc = ab+c     # Time is (a+b) + c
abcd = abc+d   # Time is (a+b+c) + d
abcde = abcd+e # Time is (a+b+c+d) + e

которые, если a, b, c, d, e примерно одинакового размера, скажем, n, являются операциями n * (n-1)/2-1 или, по существу, n-квадратами.

Чтобы получить плохое поведение для x + = y, попробуйте:

def a(n=100):
    res = ""
    for k in xrange(n):
        v=res
        res += "foobar"
    return res

Даже если v фактически не используется, достаточно вызвать медленный путь для + = и получить плохое поведение, которое беспокоит людей.

Я полагаю, что + = не было введено до Python 2.0, поэтому было невозможно добавить приложение эффективно, не используя что-то вроде ".join() в Python 1.6 и ранее.

Ответ 7

Вы измеряете две различные операции: создание массива строк и конкатенацию строк.

    import timeit
    def x():
        s = []
        for i in range(100):
            s.append("abcdefg"[i%7])
        return ''.join(s)
    def y():
        s = ''
        for i in range(100):
            s += "abcdefgh"[i%7]

    # timeit.timeit(x) returns about 32s
    # timeit.timeit(y) returns about 23s

Из вышесказанного действительно казалось бы, что "+" - это более быстрая операция, чем объединение. Но учтите:

    src = []
    def c():
        global src
        s = []
        for i in range(100):
            s.append("abcdefg"[i%7])
        src = s
    def x2():
        return ''.join(src)
    def y2():
        s = ''
        for i in range(len(src)):
            s += src[i]
        return s

    # timeit.timeit(c) returns about 30s
    # timeit.timeit(x2) returns about 1.5s
    # timeit.timeit(y2) returns about 14s

Другими словами, по времени x() vs y() ваш результат загрязняется конструкцией вашего исходного массива. Если вы нарушите это, вы обнаружите, что соединение выполняется быстрее.

Кроме того, вы работаете с небольшими массивами, и ваши временные числа просто совпадают. Если вы значительно увеличите размер массива и длину каждой строки, различия будут более ясными:

   def c2():
       global src
       s = []
       for i in range(10000):
           s.append("abcdefghijklmnopqrstuvwxyz0123456789"
       src = s

   # timeit.timeit(x2, number=10000) returns about 1s
   # timeit.timeit(y2, number=10000) returns about 80s

Ответ 8

Есть много хороших резюме уже здесь, но для большего доказательства.

Источник: я смотрел на исходный код python в течение часа и вычислил сложности!

Мои выводы.

Для 2 строк. (Предположим, что n - длина обеих строк)

Concat (+) - O(n)
Join       - O(n+k)  effectively O(n)
Format     - O(2n+k) effectively O(n)

Для более чем 2 строк. (Предположим, что n - длина всех строк)

Concat (+) - O(n^2)
Join       - O(n+k)  effectively O(n)
Format     - O(2n+k) effectively O(n)

РЕЗУЛЬТАТ:

Если у вас есть две строки, технически конкатенация (+) лучше, эффективно, хотя она точно такая же, как объединение и формат.

Если у вас более двух строк concat становится ужасным, а объединение и формат фактически одинаковы, хотя технически объединение немного лучше.

РЕЗЮМЕ:

Если вы не заботитесь об эффективности, используйте любой из вышеперечисленных. (Хотя, поскольку вы задали вопрос, я бы предположил, что вы заботитесь)

Поэтому -

Если у вас есть 2 строки, используйте concat (если не в цикле!)

Если у вас более двух строк (все строки) (или в цикле), используйте join

Если у вас есть что-то не строки, используйте формат, потому что duh.

Надеюсь, это поможет!

Ответ 9

Интересно: я сделал несколько тестов, в которых изменяется размер строки, и вот что я нашел:

def x():
    x = "a" * 100
    s=[]
    for i in range(100):
        # Other codes here...
        s.append(x)
    return ''.join(s)

def z():
    x = "a" * 100
    s=''
    for i in xrange(100):
        # Other codes here...
        s=s+x
    return s

from timeit import timeit
print "x:", timeit(x, number=1000000)
print "z:", timeit(z, number=1000000)

Для строк длины 1 (x = "a" * 1):

x: 27.2318270206
z: 14.4046051502

Для строк длиной 100:

x: 30.0796670914
z: 21.5891489983

И для строк длиной 1000, время выполнения 100 000 раз вместо 1 000 000

x: 14.1769361496
z: 31.4864079952

Что, если мое чтение Objects/stringobject.c верное, имеет смысл.

В первом чтении появляется, что алгоритм String.join(в крайнем случае):

def join(sep, sequence):
    size = 0
    for string in sequence:
        size += len(string) + len(sep)

    result = malloc(size)

    for string in sequence:
        copy string into result
        copy sep into result

    return result

Таким образом, для этого потребуется более или менее шагов O(S) (где S - сумма длин всех соединяемых строк).

Ответ 10

В дополнение к тому, что говорили другие, строки 100 1- char действительно малы. (Я как бы удивлен, что вы вообще разделяете результаты). Это набор данных, который подходит в вашем кеше процессора. Вы не увидите асимптотические характеристики на микрообъекте.

Ответ 11

Конкатенация строк была намного медленнее перед Python 2.5, когда она все еще создавала новую копию для каждой конкатенации строк, а не добавляла к оригиналу, что привело к тому, что join() стал популярным обходным решением.

Вот старый тест, демонстрирующий старую проблему: http://www.skymind.com/~ocrow/python_string/