¿Son punteros las variables de Python? O sino ¿qué son?
Las variables en Python son solo indicadores, hasta donde yo sé.
Según esta regla, puedo asumir que el resultado de este fragmento de código:
i = 5
j = i
j = 3
print(i)
sería 3
.
Pero obtuve un resultado inesperado para mí, y fue 5
...
Además, mi libro de Python cubre este ejemplo:
i = [1,2,3]
j = i
i[0] = 5
print(j)
El resultado sería [5,2,3]
.
¿Qué estoy entendiendo mal?
Los llamamos referencias. ellos funcionan asi
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)
Las entradas pequeñas están internadas, pero eso no es importante para esta explicación.
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
Las variables no son indicadores. Cuando asignas una variable, estás vinculando el nombre a un objeto. A partir de ese momento puedes referirte al objeto usando el nombre, hasta que ese nombre sea rebote.
En su primer ejemplo, el nombre i
está vinculado al valor 5
. Vincular diferentes valores al nombre j
no tiene ningún efecto i
, por lo que cuando imprima más tarde, el valor del i
valor seguirá siendo 5
.
En su segundo ejemplo, vincula ambos i
y j
al mismo objeto de lista. Cuando modifica el contenido de la lista, puede ver el cambio independientemente del nombre que utilice para referirse a la lista.
Tenga en cuenta que sería incorrecto si dijera "ambas listas han cambiado". Sólo hay una lista pero tiene dos nombres ( i
y j
) que hacen referencia a ella.
Documentación relacionada
- Modelo de ejecución: denominación y vinculación
TLDR: los nombres de Python funcionan como punteros con eliminación/referenciación automática, pero no permiten operaciones explícitas de puntero. Otros objetivos representan direcciones indirectas, que se comportan de manera similar a los punteros.
La especificación del lenguaje Python no define qué nombres y demás son realmente , solo cómo se comportan. Sin embargo, el comportamiento se puede explicar con sugerencias.
La implementación de CPython utiliza punteros de tipoPyObject*
oculto. Como tal, es posible traducir la semántica de nombres en operaciones de puntero. La clave es separar los nombres de los objetos reales .
El código Python de ejemplo incluye nombres ( i
) y objetos ( 5
).
i = 5 # name `i` refers to object `5`
j = i # ???
j = 3 # name `j` refers to object `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 lo definimos *i = five
, pero i = &five
. Los nombres y objetos existen independientemente unos de otros.
Los nombres sólo 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
de los dos 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 cortes ( 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] # name `i` refers to object `[1, 2, 3]`
j = i # name `j` refers to referent of `i`
i[0] = 5 # ???
Un CPython list
utiliza una matriz C de PyObject*
punteros bajo el 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 dos nombres.
Los valores de los objetos compuestos existentes se pueden cambiar.
Al cambiar el valor de un objeto a través de un nombre, los nombres no se cambian. Ambos i
y j
siguen refiriéndose 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.
Las variables de Python son nombres vinculados a objetos.
De los documentos :
Los nombres se refieren a objetos. Los nombres se introducen mediante operaciones de vinculación de nombres. Cada aparición de un nombre en el texto del programa se refiere a la vinculación de ese nombre establecida en el bloque de funciones más interno que contiene el uso.
Cuando tu lo hagas
i = 5
j = i
eso es lo mismo que hacer:
i = 5
j = 5
j
no señala i
y después de la tarea, j
no sabe que i
existe. j
simplemente está vinculado a lo que sea que i
apuntaba en el momento de la asignación.
Si hicieras las tareas en la misma línea, se vería así:
i = j = 5
Y el resultado sería exactamente el mismo.
Así, más tarde haciendo
i = 3
no cambia lo j
que apunta, y puedes intercambiarlo, j = 3
no cambiaría lo que i
apunta.
Su ejemplo no elimina la referencia a la lista.
Entonces cuando haces esto:
i = [1,2,3]
j = i
Es lo mismo que hacer esto:
i = j = [1,2,3]
entonces i
y j
ambos apuntan a la misma lista. Entonces tu ejemplo muta la lista:
i[0] = 5
Las listas de Python son objetos mutables, por lo que cuando cambias la lista de una referencia y la miras desde otra referencia, verás el mismo resultado porque es la misma lista.
Lo que probablemente quieras es una copia de la lista, tal vez como esta:
i = [1,2,3]
j = i.copy()
Tenga en cuenta que ambas listas contienen los mismos objetos y, si son mutables, estarán en el mismo estado mutado cuando se acceda desde ambas listas porque son los mismos objetos.