Интернирование строк Python

92

Хотя этот вопрос не имеет практического применения, мне любопытно, как Python выполняет интернирование строк. Я заметил следующее.

>>> "string" is "string"
True

Это как я и ожидал.

Вы тоже можете это сделать.

>>> "strin"+"g" is "string"
True

И это очень умно!

Но ты не можешь этого сделать.

>>> s1 = "strin"
>>> s2 = "string"
>>> s1+"g" is s2
False

Почему бы Python не оценить s1+"g"и не понять, что это то же самое, s2и не указать на тот же адрес? Что на самом деле происходит в этом последнем блоке, чтобы он вернулся False?

Зеев Г
источник

Ответы:

95

Это зависит от реализации, но ваш интерпретатор, вероятно, использует константы времени компиляции, но не результаты выражений времени выполнения.

Далее я использую CPython 2.7.3.

Во втором примере выражение "strin"+"g"вычисляется во время компиляции и заменяется на "string". Это заставляет первые два примера вести себя одинаково.

Если мы рассмотрим байт-коды, мы увидим, что они точно такие же:

  # s1 = "string"
  2           0 LOAD_CONST               1 ('string')
              3 STORE_FAST               0 (s1)

  # s2 = "strin" + "g"
  3           6 LOAD_CONST               4 ('string')
              9 STORE_FAST               1 (s2)

Третий пример включает конкатенацию во время выполнения, результат которой не интернируется автоматически:

  # s3a = "strin"
  # s3 = s3a + "g"
  4          12 LOAD_CONST               2 ('strin')
             15 STORE_FAST               2 (s3a)

  5          18 LOAD_FAST                2 (s3a)
             21 LOAD_CONST               3 ('g')
             24 BINARY_ADD          
             25 STORE_FAST               3 (s3)
             28 LOAD_CONST               0 (None)
             31 RETURN_VALUE        

Если бы вы вручную получили intern()результат третьего выражения, вы бы получили тот же объект, что и раньше:

>>> s3a = "strin"
>>> s3 = s3a + "g"
>>> s3 is "string"
False
>>> intern(s3) is "string"
True
NPE
источник
22
И для записи: оптимизация с помощью глазка Python предварительно вычисляет арифметические операции с константами ( "string1" + "s2", 10 + 3*20и т. Д.) Во время компиляции, но ограничивает результирующие последовательности всего 20 элементами (чтобы предотвратить [None] * 10**1000чрезмерное расширение вашего байт-кода). Именно эта оптимизация обрушилась "strin" + "g"на "string"; результат короче 20 символов.
Martijn Pieters
13
И чтобы сделать это вдвойне ясно: интернирования здесь вообще не происходит. Неизменяемые литералы вместо этого сохраняются как константы с байт-кодом. Интернирование действительно имеет место для имен, используемых в коде, но не для строковых значений, созданных программой, если специально не интернировано intern()функцией.
Мартейн Питерс
9
Для тех, кто пытается найти internфункцию в Python 3 - она ​​перенесена на sys.intern
Тимофей Черноусов
1

Случай 1

>>> x = "123"  
>>> y = "123"  
>>> x == y  
True  
>>> x is y  
True  
>>> id(x)  
50986112  
>>> id(y)  
50986112  

Случай 2

>>> x = "12"
>>> y = "123"
>>> x = x + "3"
>>> x is y
False
>>> x == y
True

Теперь, ваш вопрос, почему же идентификатор в случае 1 , а не в случае 2.
В случае 1, вы назначили строковый литерал "123"к xи y.

Поскольку строки неизменяемы, интерпретатору имеет смысл сохранить строковый литерал только один раз и указать все переменные на один и тот же объект.
Следовательно, вы видите идентификатор как идентичный.

В случае 2 вы изменяете xс помощью конкатенации. Оба xи yимеют одинаковые ценности, но не идентичны.
Оба указывают на разные объекты в памяти. Следовательно, они разные, idи isоператор вернулсяFalse

cppcoder
источник
Почему, поскольку строки неизменяемы, присвоение x + "3" (и поиск нового места для хранения строки) не соответствует той же ссылке, что и y?
nicecatch
Потому что тогда необходимо сравнить новую строку со всеми существующими строками; потенциально очень дорогая операция. Он мог бы сделать это в фоновом режиме после присвоения, я полагаю, чтобы уменьшить память, но тогда вы получите еще более странное поведение: id(x) != id(x)например, потому что строка была перемещена в процессе оценки.
DylanYoung
1
@AndreaConte, потому что конкатенация строк не выполняет дополнительной работы по поиску в пуле всех использованных строк каждый раз, когда создается новая. С другой стороны, интерпретатор «оптимизирует» выражение x = "12" + "3"в x = "123"(конкатенацию двух строковых литералов в одном выражении), так что присвоение фактически выполняет поиск и находит ту же «внутреннюю» строку, что и для y = "123".
derenio
На самом деле, дело не в том, что присваивание выполняет поиск, а не каждый строковый литерал из исходного кода становится «внутренним», и этот объект повторно используется во всех других местах.
derenio