Как может неназначенная строка в Python иметь адрес в памяти? - PullRequest
45 голосов
/ 03 августа 2011

Может кто-нибудь объяснить это мне?Так что я играл с командой id () в python и наткнулся на это:

>>> id('cat')
5181152
>>> a = 'cat'
>>> b = 'cat'
>>> id(a)
5181152
>>> id(b)
5181152

Это имеет какой-то смысл для меня, за исключением одной части: строка 'cat' имеет адрес в памяти передЯ назначаю это переменной.Я, вероятно, просто не понимаю, как работает адресация памяти, но может кто-то объяснить мне или, по крайней мере, сказать мне, что я должен прочитать об адресации памяти?

Так что все хорошо, но это смущало меня еще:

>>> a = a[0:2]+'t'
>>> a
'cat'
>>> id(a)
39964224
>>> id('cat')
5181152

Это показалось мне странным, потому что 'cat' - это строка с адресом 5181152, но новый a имеет другой адрес.Так что, если в памяти две строки 'cat' , почему два адреса не выводятся для id ('cat') ?Моя последняя мысль была о том, что конкатенация как-то связана с изменением адреса, поэтому я попробовал это:

>>> id(b[0:2]+'t')
39921024
>>> b = b[0:2]+'t'
>>> b
'cat'
>>> id(b)
40000896

Я бы предположил, что идентификаторы будут одинаковыми, но это не так.Мысли

Ответы [ 5 ]

52 голосов
/ 03 августа 2011

Python довольно агрессивно использует строковые литералы. Правила, по которым он это делает, зависят от реализации, но CPython использует два, о которых я знаю:

  • Строки, которые содержат только символы, допустимые в идентификаторах Python, interned, , что означает, что они хранятся в большой таблице и используются повторно везде, где они встречаются. Таким образом, независимо от того, где вы используете "cat", он всегда ссылается на один и тот же строковый объект.
  • Строковые литералы в одном и том же блоке кода используются повторно независимо от их содержания и длины. Если вы поместите строковый литерал всего адреса Геттисберга в функцию дважды, это будет один и тот же строковый объект оба раза. В отдельных функциях это разные объекты: def foo(): return "pack my box with five dozen liquor jugs" def bar(): return "pack my box with five dozen liquor jugs" assert foo() is bar() # AssertionError

Обе оптимизации выполняются во время компиляции (то есть, когда генерируется байт-код).

С другой стороны, что-то вроде chr(99) + chr(97) + chr(116) является строкой выражением , которое вычисляется как строка "cat". В динамическом языке, таком как Python, его значение не может быть известно во время компиляции (chr() - встроенная функция, но вы, возможно, переназначили ее), поэтому обычно оно не интернируется. Таким образом, id() отличается от "cat". Однако вы можете принудительно ввести строку, используя функцию intern(). Таким образом:

id(intern(chr(99) + chr(97) + chr(116))) == id("cat")   # True

Как уже упоминали другие, интернирование возможно, потому что строки неизменны. Другими словами, невозможно изменить "cat" на "dog". Вы должны сгенерировать новый строковый объект, что означает, что нет опасности, что это повлияет на другие имена, указывающие на эту строку.

Кроме того, Python также преобразует выражения, содержащие только константы (например, "c" + "a" + "t"), в константы во время компиляции, как показано в приведенной ниже разборке. Они будут оптимизированы для указания идентичных строковых объектов в соответствии с приведенными выше правилами.

>>> def foo(): "c" + "a" + "t"
...
>>> from dis import dis; dis(foo)
  1           0 LOAD_CONST               5 ('cat')
              3 POP_TOP
              4 LOAD_CONST               0 (None)
              7 RETURN_VALUE
47 голосов
/ 03 августа 2011
У

'cat' есть адрес, потому что вы создаете его, чтобы передать его id().Вы еще не связали его с именем, но объект все еще существует.

Python кэширует и повторно использует короткие строки.Но если вы собираете строки путем конкатенации, то код, который ищет в кеше и пытается повторно использовать его, игнорируется.

Обратите внимание, что внутренняя работа строкового кеша - это чистая деталь реализации и на нее не следует полагаться.1009 *

17 голосов
/ 03 августа 2011

Все значения должны находиться где-то в памяти. Вот почему id('cat') производит значение. Вы называете это «несуществующей» строкой, но она явно существует, просто ей еще не присвоено имя.

Строки являются неизменяемыми, поэтому интерпретатор может делать умные вещи, например, делать все экземпляры литерала 'cat' одним и тем же объектом, так что id(a) и id(b) одинаковы.

Работа со строками приведет к появлению новых строк. Это могут быть или не быть те же строки, что и предыдущие строки с тем же содержанием.

Обратите внимание, что все эти детали являются деталями реализации CPython, и они могут измениться в любое время. Вам не нужно беспокоиться об этих проблемах в реальных программах.

8 голосов
/ 03 августа 2011

Переменные Python довольно непохожи на переменные в других языках (скажем, C).

Во многих других языках переменная - это имя места в памяти. В этих языках различные типы переменных могут ссылаться на различные типы местоположений, и одному и тому же местоположению может быть присвоено несколько имен. По большей части в данной ячейке памяти время от времени могут меняться данные. Существуют также способы косвенного обращения к ячейкам памяти (int *p будет содержать адрес, а в ячейке памяти по этому адресу есть целое число.) Но фактическое местоположение, на которое ссылается переменная, не может измениться; Переменная является местоположением. Назначение переменной на этих языках - это «Поиск местоположения для этой переменной и копирование этих данных в это местоположение»

Python не работает таким образом. В python реальные объекты помещаются в какую-то область памяти, а переменные похожи на теги для местоположений. Python управляет сохраненными значениями отдельно от того, как он управляет переменными. По сути, присвоение в python означает «Посмотрите информацию для этой переменной, забудьте местоположение, к которому она уже относится, и замените это новым местоположением». Данные не копируются.

Общей чертой языков, которые работают как python (в отличие от первого вида, о котором мы говорили ранее), является то, что некоторые виды объектов управляются особым образом; идентичные значения кэшируются так, чтобы они не занимали дополнительную память, и чтобы их можно было очень легко сравнивать (если они имеют одинаковый адрес, они равны). Этот процесс называется interning ; Все строковые литералы Python интернированы (в дополнение к нескольким другим типам), хотя динамически создаваемые строки могут не быть.

В вашем точном коде семантическое диалоговое окно будет:

# before anything, since 'cat' is a literal constant, add it to the intern cache
>>> id('cat') # grab the constant 'cat' from the intern cache and look up 
              # it's address
5181152
>>> a = 'cat' # grab the constant 'cat' from the intern cache and 
              # make the variable "a" point to it's location 
>>> b = 'cat' # do the same thing with the variable "b"
>>> id(a) # look up the object "a" currently points to, 
          # then look up that object's address
5181152
>>> id(b) # look up the object "b" currently points to, 
          # then look up that object's address
5181152
1 голос
/ 04 августа 2011

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

>>> id('cat')
5181152

Как уже ответили другие, выполняя эти инструкции, вы заставляете виртуальную машину Python создаватьстроковый объект, содержащий строку «кошка».Этот строковый объект кэшируется и находится по адресу 5181152.

>>> a = 'cat'
>>> id(a)
5181152

Опять же, для ссылки на этот кешированный строковый объект на 5181152 был назначен символ "cat".

>>> a = a[0:2]
>>> id(a)
27731511

На данный момент в моей модифицированной версии вашей программы вы создали два небольших строковых объекта: 'cat' и 'ca'.'cat' все еще существует в кэше.Строка, на которую ссылается a, представляет собой другой и, вероятно, новый строковый объект, содержащий символы 'ca'.

>>> a = a + 't'
>>> id(a)
39964224

Теперь вы создали еще один новый строковый объект.Этот объект является конкатенацией строки 'ca' по адресу 27731511 и строки 't'.Эта конкатенация соответствует ранее кэшированной строке 'cat'.Python не обнаруживает этот случай автоматически.Как указано выше, вы можете принудительно выполнить поиск с помощью метода intern().

Надеемся, что это объяснение освещает шаги, с помощью которых изменился адрес a.

Ваш код не включалпромежуточному состоянию с a присваивается строка 'ca'.Ответ остается в силе, потому что интерпретатор Python генерирует новый строковый объект для хранения промежуточного результата a[0:2], независимо от того, назначаете ли вы этот промежуточный результат переменной или нет.

...