Указатели переменных Python? или еще какие они?

Насколько мне известно, переменные в Python - это просто указатели.

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

i = 5
j = i
j = 3 
print(i)

будет 3. Но я получил неожиданный для меня результат, он был 5.

Более того, моя книга по Python охватывает этот пример:

i = [1,2,3]
j = i
i[0] = 5
print(j)

результат будет [5,2,3].

Что я неправильно понимаю?


person Lior    schedule 23.11.2012    source источник
comment
возможный дубликат Python: как передать переменную по ссылке?   -  person Martijn Pieters    schedule 23.11.2012
comment
Я не понимаю, как i должно было быть равно 3   -  person Tooniis    schedule 22.03.2018
comment
@Tooniis j теперь указывает на другой блок памяти, отличный от того, на который указывает i. Вы, наверное, уже поняли, но на случай, если кто-то еще увидит это   -  person Swaroop Joshi    schedule 31.05.2020


Ответы (9)


Мы называем их ссылками. Они так работают

i = 5     # create int(5) instance, bind it to i
j = i     # bind j to the same int as i
j = 3     # create int(3) instance, bind it to j
print i   # i still bound to the int(5), j bound to the int(3)

Маленькие целые числа интернированы, но это не важно для данного объяснения.

i = [1,2,3]   # create the list instance, and bind it to i
j = i         # bind j to the same list as i
i[0] = 5      # change the first item of i
print j       # j is still bound to the same list as i
person John La Rooy    schedule 23.11.2012
comment
Привет, Джон, что ты имеешь в виду, говоря «Маленькие целые интернированы»? Спасибо! - person yuqli; 31.03.2018
comment
@yuqli В Python все является объектом, включая числа. Поскольку маленькие числа (-5,256) используются очень часто, они интернируются или кешируются в CPython. Таким образом, каждый раз, когда вы вводите 40, вы ссылаетесь на один и тот же объект в памяти. Чтобы увидеть этот тип, введите a, b = 256 и проверьте a is b. Теперь попробуйте с a, b = 257. См .: stackoverflow.com/a/1136852/3973834 и codementor.io/python/tutorial/ - person Evan Rosica; 13.06.2018
comment
По моему опыту, разработчики Python чаще называют их именами. Термины ссылки идут с ненужным багажом C и, возможно, слишком сильно смещают Python (язык) в сторону CPython (реализация), который использует подсчет ссылок. - person wim; 06.08.2019

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

В вашем первом примере имя i привязано к значению 5. Привязка разных значений к имени j не влияет на i, поэтому, когда вы позже распечатаете значение i, значение все равно будет 5.

Во втором примере вы привязываете i и j к одному и тому же объекту списка. Когда вы изменяете содержимое списка, вы можете видеть это изменение независимо от того, какое имя вы используете для ссылки на список.

Обратите внимание, что было бы неправильно, если бы вы сказали «оба списка изменились». Есть только один список, но у него есть два имени (i и j), которые относятся к нему.

Сопутствующая документация

person Mark Byers    schedule 23.11.2012

Переменные Python - это имена, привязанные к объектам

Из документации:

Имена относятся к объектам. Имена вводятся операциями привязки имен. Каждое вхождение имени в тексте программы относится к привязке этого имени, установленной в самом внутреннем функциональном блоке, содержащем использование.

Когда ты делаешь

i = 5
j = i

это то же самое, что и:

i = 5
j = 5

j не указывает на i, а после назначения j не знает, что i существует. j просто привязан к тому, на что i указывал во время назначения.

Если бы вы выполняли задания в одной строке, это выглядело бы так:

i = j = 5

И результат был бы точно таким же.

Таким образом, позже делая

i = 3

не меняет то, на что указывает j - и вы можете поменять местами - j = 3 не изменит то, на что указывает i.

В вашем примере не удаляется ссылка на список

Итак, когда вы это сделаете:

i = [1,2,3]
j = i

Это то же самое, что и это:

i = j = [1,2,3]

поэтому i и j указывают на один и тот же список. Затем ваш пример изменяет список:

i[0] = 5

Списки Python являются изменяемыми объектами, поэтому, если вы измените список из одной ссылки и посмотрите на него из другой ссылки, вы увидите тот же результат, потому что это тот же список.

person Aaron Hall    schedule 25.04.2016

TL; DR: имена Python работают как указатели с автоматическим удалением / ссылкой, но не допускают явных операций с указателями. Другие цели представляют собой косвенные ссылки, которые ведут себя аналогично указателям.


Спецификация языка Python не определяет, какие имена и тому подобное на самом деле есть, а определяет только то, как они себя ведут. Однако поведение можно объяснить указателями.

Реализация CPython использует указатели типа PyObject* под капот. Таким образом, можно преобразовать семантику имени в операции с указателем. Главное - отделить имена от реальных объектов.

Пример кода Python включает имена (i) и объекты (5).

i = 5  # name `i` refers to object `5`
j = i  # ???
j = 3  # name `j` refers to object `3`

Это можно грубо перевести в код C с отдельными именами и объектами.

int three=3, five=5;  // objects
int *i, *j;           // names
i = &five;   // name `i` refers to position of object `5`
j = i;       // name `j` refers to referent of `i`
j = &three;  // name `j` refers to position of object `3`

Важная часть состоит в том, что имена-указатели не хранят объекты! Мы определяли не *i = five, а i = &five . Имена и объекты существуют независимо друг от друга.

Имена только указывают на существующие объекты в памяти.

При присвоении от имени к имени никакие объекты не обмениваются! Когда мы определяем j = i, это эквивалентно j = &five. Ни i, ни j не подключены друг к другу.

+- name i -+ -\
               \
                --> + <five> -+
               /    |        5 |
+- name j -+ -/     +----------+

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


Python также имеет другие типы именованных элементов: attribute ссылки (i.j), подписки (i[j]) и нарезки (i[:j]). В отличие от имен, которые относятся непосредственно к объектам, все три косвенно относятся к элементам объектов.

Пример кода включает в себя как имена (i), так и подписку (i[0]).

i = [1,2,3]  # name `i` refers to object `[1, 2, 3]`
j = i        # name `j` refers to referent of `i`
i[0] = 5     # ???

CPython list использует под капотом C-массив PyObject* указателей. Это снова можно грубо перевести в код C с отдельными именами и объектами.

typedef struct{
    int *elements[3];
} list;  // length 3 `list` type

int one = 1, two = 2, three = 3, five = 5;
list values = {&one, &two, &three};  // objects
list *i, *j;                         // names
i = &values;             // name `i` refers to object `[1, 2, 3]`
j = i;                   // name `j` refers to referent of `i`
i->elements[0] = &five;  // leading element of `i` refers to object `5`

Важно то, что мы не меняли никаких имен! Мы изменили i->elements[0], элемент объекта, на который указывают оба наших имени.

Значения существующих составных объектов могут быть изменены.

При изменении значения объекта через имя имена не меняются. Оба i и j по-прежнему относятся к одному и тому же объекту, значение которого мы можем изменить.

+- name i -+ -\
               \
                --> + <values> -+
               /    |  elements | --> [1, 2, 3]
+- name j -+ -/     +-----------+

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

person MisterMiyagi    schedule 06.08.2019
comment
Мне очень нравится этот ответ, но я думаю, что вы перевернули назначения i и j в своем примере. Вы начинаете с i = 5, j = 3, а затем инвертируете их в остальной части вашего сообщения. Тем не менее, это единственный ответ, imo, который справедливо отвечает на вопрос в OP и действительно объясняет, что происходит под капотом. - person jeremy radcliff; 13.08.2019
comment
@jeremyradcliff Спасибо за внимание. Должен быть исправлен сейчас. Сообщите мне, если я пропустил еще что-нибудь. - person MisterMiyagi; 13.08.2019
comment
Это должен быть принятый ответ! Он решает вопрос C. Как программист на C, это единственный ответ, который я могу понять. - person pauljohn32; 03.06.2021

Это не совсем указатели, это ссылки на объекты. Объекты могут быть изменяемыми или неизменяемыми. Неизменяемый объект копируется при изменении. Изменяемый объект изменяется на месте. Целое число - это неизменяемый объект, на который вы ссылаетесь с помощью своих переменных i и j. Список - это изменяемый объект.

В вашем первом примере

i=5
# The label i now references 5
j=i
# The label j now references what i references
j=3
# The label j now references 3
print i
# i still references 5

Во втором примере:

i=[1,2,3]
# i references a list object (a mutable object)
j=i
# j now references the same object as i (they reference the same mutable object)
i[0]=5
# sets first element of references object to 5
print j
# prints the list object that j references. It's the same one as i.
person Keith    schedule 23.11.2012
comment
Неизменяемый объект копируется при изменении. Это немного противоречиво. - person PM 2Ring; 07.08.2019

Назначение не изменяет объекты; все, что он делает, это меняет точку, на которую указывает переменная. Изменение того, где указывает одна переменная, не изменится, где указывает другая.

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

x = []
y = x
x.append(1)
# x and y both are now [1]

Но присваивание по-прежнему просто перемещает указатель:

x = [2]
# x now points to new list [2]; y still points to old list [1]

Числа, в отличие от словарей и списков, неизменяемы. Если вы делаете x = 3; x += 2, вы не превращаете число 3 в число 5; вместо этого вы просто указываете переменной x на 5. Число 3 по-прежнему остается неизменным, и любые переменные, указывающие на него, по-прежнему будут видеть 3 в качестве своего значения.

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

person Mark Reed    schedule 23.11.2012
comment
Это не то, что означает тип значения. Тип значения означает именно то, что вы описали в последнем абзаце (что значение передается / копируется вместо ссылки на объект), и это не похоже на это внутри (в CPython и в PyPy без JIT-компилятор - каждое целое число является объектом, размещенным в куче). Просто придерживайтесь неизменяемого, это именно то слово, которое вам здесь нужно. - person ; 23.11.2012

Когда вы устанавливаете j=3, метка j больше не применяется (указывает) к i, она начинает указывать на целое число 3. Имя i по-прежнему относится к значению, которое вы изначально установили, 5.

person mbatchkarov    schedule 23.11.2012

какой бы переменной ни было слева от знака '=' присваивается значение справа от '='

i = 5

j = i --- j имеет 5

j = 3 --- j имеет 3 (перезаписывает значение 5), но относительно i ничего не изменилось

_4 _-- так это печатает 5

person vijaya karthavya kudithipudi    schedule 17.03.2017

В Python все является объектом, включая сами части памяти, которые вам возвращают. Это означает, что когда создается новый фрагмент памяти (независимо от того, что вы создали: int, str, настраиваемый объект и т. Д.), У вас появляется новый объект памяти. В вашем случае это присвоение 3, которое создает новый объект (память) и, следовательно, имеет новый адрес.

Если вы выполните следующее, вы легко поймете, что я имею в виду.

i = 5
j = i
print("id of j: {}", id(j))
j = 3
print("id of j: {}", id(j))

ИМО, с точки зрения памяти, это ключевое понимание / различие между C и Python. В C / C ++ вы возвращаете указатель памяти (если вы, конечно, используете синтаксис указателя) вместо объекта памяти, что дает вам больше гибкости с точки зрения изменения указанного адреса.

person stdout    schedule 11.08.2017