Почему это так медленно при назначении объединенной строки переменной в Python?

Если это только конкатенация строк следующим образом, она завершается немедленно.

test_str = "abcdefghijklmn123456789"
str1 = ""
str2 = ""

start = time.time()
for i in range(1, 100001):

    str1 = str1 + test_str
    str2 = str2 + test_str

    if i % 20000 == 0:
        print("time(sec) => {}".format(time.time() - start))
        start = time.time()

Постоянное время обработки

time(sec) => 0.013324975967407227
time(sec) => 0.020363807678222656
time(sec) => 0.009979963302612305
time(sec) => 0.01744699478149414
time(sec) => 0.0227658748626709

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

test_str = "abcdefghijklmn123456789"
str1 = ""
str2 = ""

start = time.time()
for i in range(1, 100001):

    str1 = str1 + test_str
    # str2 = str2 + test_str
    # ↓
    str2 = str1

    if i % 20000 == 0:
        print("time(sec) => {}".format(time.time() - start))
        start = time.time()

Время обработки будет отложено.

time(sec) => 0.36466407775878906
time(sec) => 1.105351209640503
time(sec) => 2.6467738151550293
time(sec) => 5.891657829284668
time(sec) => 9.266698360443115

И python2, и python3 дают одинаковый результат.

Ответы

Ответ 1

В целом, стандарт языка Python здесь не дает никаких гарантий; на самом деле, как определено, строки являются неизменяемыми, и то, что вы делаете, должно укусить вас в любом случае, так как вы написали форму алгоритма Шлемьеля Пейнтера.

Но в первом случае, как деталь реализации, CPython (справочный интерпретатор) поможет вам и объединит строку на месте (технически нарушая гарантию неизменности) при некоторых довольно специфических условиях, которые позволяют ему придерживаться духа правила неизменности. Наиболее важным условием является то, что на конкатенируемую строку следует ссылаться только в одном месте (если это не так, другая ссылка будет меняться на месте, нарушая внешний вид str являющегося неизменным). str2 = str1 после каждой конкатенации, вы гарантируете, что при конкатенации есть две ссылки, поэтому при каждой конкатенации необходимо создавать новую str чтобы сохранить кажущуюся неизменность строк. Это означает больше выделения и освобождения памяти, большее (и постепенно увеличивающееся) количество копий памяти и т.д.

Обратите внимание, что полагаться на эту оптимизацию явно не рекомендуется в PEP 8, руководстве по стилю Python:

  • Код должен быть написан так, чтобы не мешать другим реализациям Python (PyPy, Jython, IronPython, Cython, Psyco и т.д.).

    Например, не полагайтесь на эффективную реализацию CPython конкатенации строк на месте для операторов в форме a += b или a = a + b. Эта оптимизация хрупка даже в CPython (она работает только для некоторых типов) и совсем не присутствует в реализациях, которые не используют пересчет. В чувствительных к производительности частях библиотеки следует использовать форму ''.join(). Это будет гарантировать, что конкатенация происходит в линейное время в разных реализациях.

Важное замечание о том, что "работает только для некоторых типов". Эта оптимизация относится только к str; в Python 2 он не работает на unicode (хотя Python 3 str основан на реализации unicode Python 2), а в Python 3 он не работает на bytes (которые похожи на Python 2 str под капотом),