¿Son punteros las variables de Python? o si no, ¿qué son?

81

Las variables en Python son solo punteros, 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í, lo 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?

Lior
fuente
posible duplicado de Python: ¿Cómo paso una variable por referencia?
Martijn Pieters
5
No veo cómo idebería haber sido igual a3
Tooniis
@Tooniis j ahora apunta a un bloque de memoria diferente, distinto al que estoy apuntando. Probablemente ya lo haya entendido, pero en caso de que alguien más vea esto
Swaroop Joshi

Respuestas:

88

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)

Los pequeños entres están internados, 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
John La Rooy
fuente
3
Hola John, ¿a qué te refieres con "Los pequeños entrados están internos"? ¡Gracias!
Yuqli
6
@yuqli En Python, todo es un objeto, incluidos los números. Dado que se utilizan números pequeños (-5,256) con mucha frecuencia, se "internan" o se almacenan en caché en CPython. Por lo tanto, cada vez que escribe 40, hace referencia al mismo objeto en la memoria. Para ver este tipo a, b = 256 y prueba a is b. Ahora inténtelo con a, b = 257. Ver: stackoverflow.com/a/1136852/3973834 y codementor.io/python/tutorial/…
Evan Rosica
3
En mi experiencia, llamarlos "nombres" es más común entre los desarrolladores de Python. El término "referencias" viene con un bagaje C innecesario, y quizás esté sesgando demasiado a Python ( el lenguaje ) hacia CPython ( la implementación ), que usa el conteo de referencias.
wim
33

Las variables no son indicadores. Cuando asigna a una variable, está vinculando el nombre a un objeto. A partir de ese momento, puede hacer referencia al objeto utilizando el nombre, hasta que ese nombre se recupere.

En su primer ejemplo, el nombre iestá vinculado al valor 5. La vinculación de diferentes valores al nombre jno tiene ningún efecto i, por lo que cuando imprime más tarde, el valor del ivalor sigue siendo 5.

En su segundo ejemplo, vincula ambos iy jal mismo objeto de lista. Cuando modifica el contenido de la lista, puede ver el cambio independientemente del nombre que utilice para hacer referencia a la lista.

Tenga en cuenta que sería incorrecto decir "ambas listas han cambiado". Solo hay una lista pero tiene dos nombres ( iy j) que se refieren a ella.

Documentación relacionada

Mark Byers
fuente
15

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 al enlace de ese nombre establecido 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

jno señala i, y después de la tarea, jno sabe que iexiste. jestá simplemente vinculado a lo que sea que esté iapuntando en el momento de la asignación.

Si hiciera las asignaciones 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 que jestá apuntando, y puede intercambiarlo, j = 3no cambiaría lo que iestá apuntando.

Tu 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 iy jambos 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 cambia la lista de una referencia y la mira desde otra referencia, verá el mismo resultado porque es la misma lista.

Aaron Hall
fuente
9

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  # 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 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 ini jestá 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]  # name `i` refers to object `[1, 2, 3]`
j = i        # name `j` refers to referent of `i`
i[0] = 5     # ???

Un CPython listusa 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 iy jtodaví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.

MisterMiyagi
fuente
1
Realmente me gusta esta respuesta, pero creo que invirtió las asignaciones iy jen su ejemplo. Se empieza con i = 5, j = 3y luego invertirlos en el resto de su puesto. Dicho esto de nuevo, esta es la única respuesta en mi opinión que hace justicia a la pregunta en el OP y realmente explica lo que sucede bajo el capó.
jeremy radcliff
1
@jeremyradcliff Gracias por el aviso. Debería arreglarse ahora. Avísame si me perdí más.
MisterMiyagi
7

No son del todo apuntadores, son referencias a objetos. Los objetos pueden ser mutables o inmutables. Un objeto inmutable se copia cuando se modifica. Un objeto mutable se modifica in situ. Un número entero es un objeto inmutable, al que hace referencia mediante sus variables i y j. Una lista es un objeto mutable.

En tu primer ejemplo

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

En su segundo ejemplo:

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.
Keith
fuente
"Un objeto inmutable se copia cuando se modifica". Eso es un poco contradictorio.
PM 2 Ring
1

Cuando establece que j=3la etiqueta jya no se aplica (señala) a i, comienza a apuntar al número entero 3. El nombre itodavía se refiere al valor que estableció originalmente 5,.

mbatchkarov
fuente
1

cualquier variable que esté en el lado izquierdo del signo '=' se asigna con el valor en el lado derecho de '='

i = 5

j = i --- j tiene 5

j = 3 --- j tiene 3 (sobrescribe el valor de 5) pero no se ha cambiado nada con respecto a i

print(i)- entonces esto imprime 5

vijaya karthavya kudithipudi
fuente
1

La asignación no modifica los objetos; todo lo que hace es cambiar donde apunta la variable. Cambiar dónde apunta una variable no cambiará dónde apunta otra.

Probablemente esté pensando en el hecho de que las listas y los diccionarios son tipos mutables. Hay operadores para modificar los objetos reales en el lugar, y si usa uno de ellos, verá el cambio en todas las variables que apuntan al mismo objeto:

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

Pero la asignación aún mueve el puntero:

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

Los números, a diferencia de los diccionarios y las listas, son inmutables. Si lo hace x = 3; x += 2, no está transformando el número 3 en el número 5; en su lugar, solo está haciendo que la variable xapunte a 5. El 3 sigue ahí sin cambios, y cualquier variable que apunte a él seguirá viendo 3 como su valor.

(En la implementación real, los números probablemente no sean tipos de referencia en absoluto; es más probable que las variables realmente contengan una representación del valor directamente en lugar de apuntar a él. Pero ese detalle de implementación no cambia la semántica en lo que respecta a los tipos inmutables .)

Mark Reed
fuente
1
Eso no es lo que significa el tipo de valor. Medios tipo de valor, precisamente, lo que se describe en el último párrafo (que se pasa el valor / copiado alrededor en lugar de una referencia al objeto), y es no así internamente (en CPython, y en PyPy sans compilador JIT - cada entero es una objeto asignado al montón). Limítese a inmutable, esa es precisamente la palabra que necesita allí.
-1

En Python, todo es objeto, incluidas las piezas de memoria que le devuelven. Eso significa que, cuando se crea un nuevo fragmento de memoria (independientemente de lo que haya creado: int, str, objeto personalizado, etc.), tiene un nuevo objeto de memoria. En su caso, esta es la asignación a 3 que crea un nuevo objeto (memoria) y, por lo tanto, tiene una nueva dirección.

Si ejecuta lo siguiente, verá lo que quiero decir fácilmente.

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

En mi opinión, en cuanto a la memoria, esta es la comprensión / diferencia clave entre C y Python. En C / C ++, se le devuelve un puntero de memoria (si usa sintaxis de puntero, por supuesto) en lugar de un objeto de memoria que le brinda más flexibilidad en términos de cambiar la dirección referida.

stdout
fuente