¿Qué sucede cuando asigna el valor de una variable a otra variable en Python?


80

Este es mi segundo día de aprendizaje de Python (conozco los conceptos básicos de C ++ y algo de OOP), y tengo una ligera confusión con respecto a las variables en Python.

Así es como los entiendo actualmente:

Las variables de Python son referencias (¿o punteros?) A objetos (que son mutables o inmutables). Cuando tenemos algo así num = 5, el objeto inmutable 5se crea en algún lugar de la memoria y el par de referencia nombre-objeto numse crea en un cierto espacio de nombres. Cuando lo tenemos a = num, no se copia nada, pero ahora ambas variables se refieren al mismo objeto y ase agregan al mismo espacio de nombres.

Aquí es donde mi libro, Automatizar las cosas aburridas con Python , me confunde. Como es un libro para principiantes, no menciona objetos, espacios de nombres, etc., e intenta explicar el siguiente código:

>>> spam = 42
>>> cheese = spam
>>> spam = 100
>>> spam
100
>>> cheese
42

La explicación que ofrece es exactamente la misma que la de un libro de C ++, lo que no me satisface ya que estamos tratando con referencias / punteros a objetos. Entonces, en este caso, supongo que en la tercera línea, como los enteros son inmutables, spamse le está asignando un puntero / referencia completamente nuevo a una ubicación diferente en la memoria, es decir, la memoria a la que apuntaba inicialmente no se modificó. De ahí que nos cheeserefiramos al objeto inicial al que se refiere spam. ¿Es esta la explicación correcta?



5
Guardaste 42en spam, Ahora almacenaste spamen medios de queso cheese = 42, Después de eso reemplazaste spama 100, No editaste, cheesepor eso cheeseestá quieto 42.

15
Como señala Jonas en su respuesta, la inmutabilidad es una pista falsa aquí: obtendría exactamente el mismo resultado si usara un objeto mutable como una lista. El caso es que reasigne la variable a una nueva referencia.
Daniel Roseman


27
@mini: prefiero pensar en las variables de Python como etiquetas en lugar de depósitos. Pegaste la etiqueta spamen el número 42. Luego, pegaste la etiqueta cheeseen la cosa etiquetada como spam(no encima de la etiqueta en sí, claro). Luego le spamquitó la etiqueta y la puso en el número 100.
Tim Pederick

Respuestas:


81

Como desarrollador de C ++, puede pensar en las variables de Python como punteros.

Por lo tanto, cuando escribe spam = 100, esto significa que "asigna el puntero", que antes apuntaba al objeto 42, para que apunte al objeto 100.

Anteriormente, cheesese asignó para apuntar al mismo objeto que se spamseñaló, que resultó ser 42en ese momento. Como no ha modificado cheese, todavía apunta a 42.

La inmutabilidad no tiene nada que ver con esto en este caso, ya que la asignación de puntero no cambia nada sobre el objeto al que se apunta.


2
igual que los objetos en js
Dushyant Bangal

7
La inmutabilidad es importante porque significa que puede tratar con seguridad la referencia como si fuera un valor. Tratar los objetos mutables como si fueran valores es más arriesgado.
Plugwash

21

A mi modo de ver, hay diferentes visiones de un idioma.

  • La perspectiva del "abogado de idiomas".
  • La perspectiva del "programador práctico".
  • la perspectiva del "implementador".

Desde la perspectiva del abogado del lenguaje, las variables de Python siempre "apuntan" a un objeto. Sin embargo, a diferencia de Java y C ++, el comportamiento de == <=> = etc depende del tipo de tiempo de ejecución de los objetos a los que apuntan las variables. Además, en Python, la gestión de la memoria está a cargo del lenguaje.

Desde la perspectiva de un programador práctico, podemos tratar el hecho de que los enteros, cadenas, tuplas, etc. son objetos inmutables * en lugar de valores simples como un detalle irrelevante. La excepción es cuando se almacenan grandes cantidades de datos numéricos, es posible que deseemos usar tipos que puedan almacenar los valores directamente (por ejemplo, matrices numpy) en lugar de tipos que terminarán con una matriz llena de referencias a objetos pequeños.

Desde la perspectiva de los implementadores, la mayoría de los lenguajes tienen algún tipo de regla como si, de modo que si los comportamientos especificados son correctos, la implementación es correcta independientemente de cómo se hagan las cosas bajo el capó.

Entonces, sí, su explicación es correcta desde la perspectiva de un abogado de idiomas. Su libro es correcto desde la perspectiva de un programador práctico. Lo que realmente hace una implementación depende de la implementación. En cpython, los enteros son objetos reales, aunque los enteros de valor pequeño se toman de un grupo de caché en lugar de crearlos de nuevo. No estoy seguro de qué hacen las otras implementaciones (por ejemplo, pypy y jython).

* observe la distinción entre objetos mutables e inmutables aquí. Con un objeto mutable tenemos que tener cuidado de tratarlo "como un valor" porque algún otro código podría mutarlo. Con un objeto inmutable no tenemos tales preocupaciones.


20

Es cierto que puede utilizar más o menos variables como punteros. Sin embargo, el código de ejemplo ayudaría mucho a explicar cómo funciona esto realmente.

Primero, utilizaremos mucho la idfunción:

Devuelve la "identidad" de un objeto. Este es un número entero que se garantiza que será único y constante para este objeto durante su vida útil. Dos objetos con vidas útiles que no se superponen pueden tener el mismo valor de id ().

Es probable que esto devuelva diferentes valores absolutos en su máquina.

Considere este ejemplo:

>>> foo = 'a string'
>>> id(foo) 
4565302640
>>> bar = 'a different string'
>>> id(bar)
4565321816
>>> bar = foo
>>> id(bar) == id(foo)
True
>>> id(bar)
4565302640

Puedes ver eso:

  • El foo / bar original tiene diferentes ID, porque apuntan a diferentes objetos
  • Cuando se asigna bar a foo, sus identificadores ahora son los mismos. Esto es similar a que ambos apuntan a la misma ubicación en la memoria que ves al hacer un puntero C ++

cuando cambiamos el valor de foo, se asigna a una identificación diferente:

>>> foo = 42
>>> id(foo)
4561661488
>>> foo = 'oh no'
>>> id(foo)
4565257832

Una observación interesante también es que los enteros tienen implícitamente esta funcionalidad hasta 256:

>>> a = 100
>>> b = 100
>>> c = 100
>>> id(a) == id(b) == id(c)
True

Sin embargo, más allá de 256, esto ya no es cierto:

>>> a = 256
>>> b = 256
>>> id(a) == id(b)
True
>>> a = 257
>>> b = 257
>>> id(a) == id(b)
False

sin embargo, la asignación aa bmantendrá la identificación igual que se muestra antes:

>>> a = b
>>> id(a) == id(b)
True

18

Python no es paso por referencia ni paso por valor. Las variables de Python no son punteros, no son referencias, no son valores. Las variables de Python son nombres .

Piense en ello como "pasar-por-alias" si necesita el mismo tipo de frase, o posiblemente "pasar-por-objeto", porque puede mutar el mismo objeto de cualquier variable que lo indique, si es mutable, pero reasignación de una variable (alias) solo cambia esa variable.

Si ayuda: las variables C son cajas en las que escribe valores. Los nombres de Python son etiquetas que colocas en los valores.

El nombre de una variable de Python es una clave en el espacio de nombres global (o local), que es efectivamente un diccionario. El valor subyacente es algún objeto en la memoria. La asignación le da un nombre a ese objeto. La asignación de una variable a otra variable significa que ambas variables son nombres para el mismo objeto. La reasignación de una variable cambia qué objeto es nombrado por esa variable sin cambiar la otra variable. Ha movido la etiqueta, pero no ha cambiado el objeto anterior ni ninguna otra etiqueta.

En el código C subyacente de la implementación de CPython, cada objeto de Python es un PyObject*, por lo que puede pensar que funciona como C si solo tuviera punteros a datos (sin punteros a punteros, sin valores pasados ​​directamente).

se podría decir que Python es paso por valor, donde los valores son punteros… o podría decir que Python es paso por referencia, donde las referencias son copias.


1
El problema de llamarlo "pasar por nombre" es que ya existe una convención de paso de parámetros llamada "llamar por nombre", con un significado completamente diferente. En la llamada por nombre, la expresión del parámetro se evalúa cada vez que la función usa el parámetro y nunca se evalúa si la función no usa el parámetro.
user2357112 apoya a Monica el

11

Cuando ejecuta spam = 100python crea un objeto más en la memoria pero no cambia el existente. así que todavía tienes el puntero cheeseal 42 y spamal 100


8

Lo que está sucediendo en spam = 100línea es el reemplazo del valor anterior (puntero al objeto de tipo intcon valor 42) con otro puntero a otro objeto (tipo int, valor 100)


Los enteros son objetos de valor que están en la pila, ¿verdad?
Gert Kommer

Sí, son algo así como un objeto que crea usando la new Class()sintaxis en C ++. Además, en Python, cualquier cosa es una instancia de objectclase / subclase.
bakatrouble

4
@GertKommer en CPython al menos, todos los objetos viven en el montón. No hay distinción de un "objeto de valor". Solo hay objetos y todo es un objeto . Es por eso que el tamaño de un int típico es de alrededor de 28 bytes, dependiendo de la versión de Python, porque tiene toda la sobrecarga de Py_Object. Las entradas pequeñas se almacenan en caché como una optimización de CPython.
juanpa.arrivillaga

8

Como mencionó @DeepSpace en los comentarios, Ned Batchelder hace un gran trabajo desmitificando variables (nombres) y asignaciones a valores en un blog, desde el cual pronunció una charla en PyCon 2015, Facts and Myths about Python nombres y valores . Puede ser útil para los Pythonistas en cualquier nivel de dominio.


1

En Python, una variable contiene la referencia al objeto . Un objeto es una porción de memoria asignada que contiene un valor y un encabezado . El encabezado del objeto contiene su tipo y un contador de referencia que indica la cantidad de veces que se hace referencia a este objeto en el código fuente para que Garbage Collection pueda identificar si un objeto se puede recolectar.

Ahora, cuando asigna valores a una variable, Python realmente asigna referencias que son punteros a ubicaciones de memoria asignadas a objetos:

# x holds a reference to the memory location allocated for  
# the object(type=string, value="Hello World", refCounter=1)

x = "Hello World" 

Ahora, cuando asigna objetos de diferente tipo a la misma variable, en realidad cambia la referencia para que apunte a un objeto diferente (es decir, una ubicación de memoria diferente). En el momento en que asigne una referencia diferente (y por lo tanto, un objeto) a una variable, el recolector de basura recuperará inmediatamente el espacio asignado al objeto anterior, asumiendo que ninguna otra variable del código fuente hace referencia a él:

# x holds a reference to the memory location allocated for  
# the object(type=string, value="Hello World", refCounter=1)

x = "Hello World" 

# Now x holds the reference to a different object(type=int, value=10, refCounter=1)
# and object(type=string, value="Hello World", refCounter=0) -which is not refereced elsewhere
# will now be garbage-collected.
x = 10

Llegando a tu ejemplo ahora,

spam contiene la referencia al objeto (tipo = int, valor = 42, refCounter = 1):

>>> spam = 42

Ahora cheesetambién tendrá la referencia al objeto (tipo = int, valor = 42, refCounter = 2)

>>> cheese = spam

Ahora el spam contiene una referencia a un objeto diferente (type = int, value = 100, refCounter = 1)

>>> spam = 100
>>> spam
100

Pero el queso seguirá apuntando al objeto (tipo = int, valor = 42, refCounter = 1)

>>> cheese
42

0

Cuando almacena spam = 42, crea un objeto en la memoria. Luego asigna cheese = spam, Asigna el objeto referenciado por spama cheese. Y finalmente, cuando cambia spam = 100, cambia solo spamobjeto. Entonces cheese = 42.


7
"Luego asigna cheese = spam, crea otro objeto en la memoria" No, no lo hace. Asigna el objeto al que hace referencia spama cheese. No se crean nuevos objetos.
juanpa.arrivillaga

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.