Странности при работе со строками

Известно, что для экономии памяти Python при работе со строками не создает разные объекты (по ссылкам передаёт в переменные адрес):

s1 = 'bar'
s2 = 'bar'
print(id(s1) == id(s2))
>>> True
s3 = 'bar'*2500
s4 = 'bar'*2500
print(id(s3) == id(s4))
>>> False

Почему так происходит?


Ответы (1 шт):

Автор решения: CrazyElf

Поздравляю, вы наткнулись на явление, которое называется string interning и которое есть не только в Python, но и в других языках (Java, C# и др.), потому что строки занимают довольно много памяти и если они часто повторяются, то хочется сэкономить память и не хранить много копий одинаковых строк.

Но детали реализации тут очень сильно зависят от конкретного интерпретатора, на разных версиях Python строки могут попадать в пул строк при разных условиях - в зависимости от длины строк, сложности их "вычисления" и т.д. Строки, задаваемые не явно в коде программы, а создаваемые в процессе работы с учётом значений переменных, уже не попадают в пул автоматически, но могут быть помещены туда с помощью специальной функции, в Python это функция sys.intern.

Чуть более подробно можно почитать, например, здесь. Вообще, очень рекомендую почитать весь текст "WTF Python!" по ссылке, а не только указанный абзац, там много интересного и про внутреннее устройство и про необычное поведение Python в каких-то ситуациях.

Я провёл небольшой эксперимент в Google Colab. Получилось, что там Python интернирует строки длиной до 20 символов, не более.

for i in range(1,100000):
    a = eval(f'"a" * {i}')
    b = eval(f'"a" * {i}')
    if(id(a) != id(b)):
        print(f'String interning works for string len <= {i - 1}')
        break

Вывод:

String interning works for string len <= 20

Потом попробовал принудительное интернирование. Оно работало пока не кончилась память:

from sys import intern
i = 1
while True:
    print(i, flush=True)
    a = intern("a" * i)
    b = intern("a" * i)
    if(id(a) != id(b)):
        print(f'String interning works for string len < {i}')
        break
    i *= 10

Вывод:

1
10
100
1000
10000
100000
1000000
10000000
100000000
1000000000
10000000000
WARNING:root:kernel restarted
→ Ссылка