Парадокс идентичности строк

Я полностью застрял в этом

>>> s = chr(8263)
>>> x = s[0]
>>> x is s[0]
False

Как это возможно? Означает ли это, что доступ к строковому символу путем индексирования создает новый экземпляр того же символа? Пусть эксперимент:

>>> L = [s[0] for _ in range(1000)]
>>> len(set(L))
1
>>> ids = map(id, L)
>>> len(set(ids))
1000
>>>

Похоже на то, что пустая трата байтов;) Или это означает, что str.__getitem__ имеет скрытую функцию? Может кто-нибудь объяснить?

Но это не конец моего удивления:

>>> s = chr(8263)
>>> t = s
>>> print(t is s, id(t) == id(s))
True True

Это ясно: t является псевдонимом для s, поэтому они представляют один и тот же объект и идентичности совпадают. Но опять же, как возможно следующее:

>>> print(t[0] is s[0])
False

s и t - это тот же объект, что?

Но хуже:

>>> print(id(t[0]) == id(s[0]))
True

t[0] и s[0] не были собраны мусором, считаются одним и тем же объектом оператором is, но имеют разные идентификаторы? Может кто-нибудь объяснить?

Ответ 1

Здесь есть два момента.

Во-первых, Python действительно создает новый символ с вызовом __getitem__, но только если этот символ имеет порядковое значение больше 256.

Например:

>>> s = chr(256)
>>> s[0] is s
True

>>> t = chr(257)
>>> t[0] is t
False

Это происходит из-за того, что скомпилированная функция getitem проверяет порядковое значение одиночного chracter и вызывает get_latin1_char, если это значение равно 256 или меньше. Это позволяет использовать односимвольные строки. В противном случае создается новый объект unicode.

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

>>> s = t # = chr(257)
>>> t[0] is s[0]
False

Сначала Python создает две новые строки с одним символом, а затем сравнивает их адреса памяти. Они имеют разные адреса (у нас есть разные объекты в соответствии с приведенным выше объяснением), поэтому сравнение объектов с is возвращает False.

С другой стороны, мы можем иметь кажущуюся парадоксальную ситуацию:

>>> id(t[0]) == id(s[0])
True

Но это связано с тем, что интерпретатор быстро повторно использует адрес памяти t[0], когда он создает новую строку s[0] в более поздний момент времени.

Если вы изучите байт-код, который производит эта строка (например, с помощью dis - см. ниже), вы увидите, что адреса для каждой стороны распределены один за другим (создается новый строковый объект, а затем вызывается id в теме).

Ссылки на объект t[0] возвращаются к нулю, как только возвращается id(t[0]) (сейчас мы делаем сравнение по целым числам, а не по самому объекту). Это означает, что s[0] может повторно использовать один и тот же адрес памяти, когда он будет создан впоследствии.


Вот разобранный байт-код для строки id(t[0]) == id(s[0]), которую я аннотировал.

Вы можете видеть, что время жизни t[0] заканчивается до создания s[0] (ссылки на него отсутствуют), поэтому его память может быть повторно использована.

  2           0 LOAD_GLOBAL              0 (id)
              3 LOAD_GLOBAL              1 (t)
              6 LOAD_CONST               1 (0)
              9 BINARY_SUBSCR                     # t[0] is created
             10 CALL_FUNCTION            1        # id(t[0]) is computed...
                                                  # ...lifetime of string t[0] over
             13 LOAD_GLOBAL              0 (id)
             16 LOAD_GLOBAL              2 (s)
             19 LOAD_CONST               1 (0)
             22 BINARY_SUBSCR                     # s[0] is created...
                                                  # ...free to reuse t[0] memory
             23 CALL_FUNCTION            1        # id(s[0]) is computed
             26 COMPARE_OP               2 (==)   # the two ids are compared
             29 RETURN_VALUE

Ответ 2

is сравнить идентификаторы и == сравнить значения. Проверьте doc

Каждый объект имеет идентификатор, тип и значение. Идентификатор объектов никогда не изменяется после его создания; вы можете думать об этом как о адреса объектов в памяти. Оператор 'is' сравнивает тождество два объекта; функция id() возвращает целое число, представляющее его (в настоящее время реализуется как его адрес). Тип объектов также неизменяемый.