TLDR: los nombres de Python funcionan como punteros con des / referenciación automática pero no permiten operaciones de puntero explícitas. Otros objetivos representan indirecciones, que se comportan de manera similar a los punteros.
La implementación de CPython usa punteros de tipoPyObject*
bajo el capó. Como tal, es posible traducir la semántica de nombres a operaciones de puntero. La clave es separar los nombres de los objetos reales .
El código de Python de ejemplo incluye tanto nombres ( i
) como objetos ( 5
).
i = 5
j = i
j = 3
Esto se puede traducir aproximadamente a código C con nombres y objetos separados .
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`
¡La parte importante es que los "nombres como punteros" no almacenan objetos! No definimos *i = five
, pero i = &five
. Los nombres y los objetos existen independientemente unos de otros.
Los nombres solo apuntan a objetos existentes en la memoria.
Al asignar un nombre a otro, no se intercambian objetos. Cuando definimos j = i
, esto es equivalente a j = &five
. Ninguno i
ni j
está conectado con el otro.
+- name i -+ -\
\
--> + <five> -+
/ | 5 |
+- name j -+ -/ +----------+
Como resultado, cambiar el destino de un nombre no afecta al otro . Solo actualiza lo que apunta ese nombre específico.
Python también tiene otros tipos de elementos similares a nombres : referencias de atributos ( i.j
), suscripciones ( i[j]
) y segmentaciones ( i[:j]
). A diferencia de los nombres, que se refieren directamente a objetos, los tres se refieren indirectamente a elementos de objetos.
El código de ejemplo incluye nombres ( i
) y una suscripción ( i[0]
).
i = [1,2,3]
j = i
i[0] = 5
Un CPython list
usa una matriz C de PyObject*
punteros debajo del capó. De nuevo, esto se puede traducir aproximadamente a código C con nombres y objetos separados.
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`
¡Lo importante es que no cambiamos ningún nombre! Cambiamos i->elements[0]
, el elemento de un objeto al que apuntan nuestros nombres.
Se pueden cambiar los valores de los objetos compuestos existentes.
Cuando se cambia el valor de un objeto mediante un nombre, los nombres no se cambian. Ambos i
y j
todavía se refieren al mismo objeto, cuyo valor podemos cambiar.
+- name i -+ -\
\
--> + <values> -+
/ | elements | --> [1, 2, 3]
+- name j -+ -/ +-----------+
El objeto intermedio se comporta de manera similar a un puntero en el sentido de que podemos cambiar directamente a qué apunta y hacer referencia a él desde varios nombres.