Странности при работе со строками
Известно, что для экономии памяти Python при работе со строками не создает разные объекты (по ссылкам передаёт в переменные адрес):
s1 = 'bar'
s2 = 'bar'
print(id(s1) == id(s2))
>>> True
s3 = 'bar'*2500
s4 = 'bar'*2500
print(id(s3) == id(s4))
>>> False
Почему так происходит?
Ответы (1 шт):
Поздравляю, вы наткнулись на явление, которое называется 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