Подтвердить что ты не робот

Как может непривязанная строка в Python иметь адрес в памяти?

Может кто-нибудь объяснить это мне? Поэтому я играл с командой 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

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

4b9b3361

Ответ 1

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

  • Строки, содержащие только символы, действительные в идентификаторах Python, интернированы, что означает, что они хранятся в большой таблице и повторно используются везде, где они происходят. Таким образом, независимо от того, где вы используете "cat", он всегда ссылается на один и тот же строковый объект.
  • Строковые литералы в одном и том же блоке кода используются повторно независимо от их содержимого и длины. Если вы поместите строковый литерал всего адреса Gettysburg в функцию, дважды, это тот же строковый объект оба раза. В отдельных функциях они представляют собой разные объекты: 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

Ответ 2

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

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

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

Ответ 3

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

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

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

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

Ответ 4

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

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

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

Общей особенностью langauges, которые работают как 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 address
5181152
>>> a = 'cat' # grab the constant 'cat' from the intern cache and 
              # make the variable "a" point to it 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 address
5181152
>>> id(b) # look up the object "b" currently points to, 
          # then look up that object address
5181152

Ответ 5

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

>>> id('cat')
5181152

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

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

Опять же, a был назначен для ссылки на этот кешированный строковый объект на 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], назначая этот промежуточный результат переменной или нет.