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
i
должно было быть равно3
- person Tooniis   schedule 22.03.2018