En esta lección se comentan aspectos avanzados de las variables.
En Python, cada dato (número, cadena, lista, etc.) que aparece en un programa es un objeto. Cada objeto tiene un identificador, un tipo y un valor:
Cada objeto tiene un identificador único que se puede conocer mediante la función id():
>>> id(5) # Este es el identificador del objeto número entero "5" 505894368 >>> id(3.14) # Este es el identificador del objeto número decimal "3.14" 46522944 >>> id("hola") # Este es el identificador del objeto cadena "hola" 47533536 >>> id([3, 4]) # Este es el identificador del objeto lista "[3, 4]" 47108264
En la versión de Python para Windows (programada en C, por lo que a veces se denomina CPython), el identificador de un objeto es su posición en la memoria del ordenador, pero podría ser cualquier otro valor, lo único importante es que cada objeto tenga un identificador distinto.
Cada objeto tiene un tipo que se puede conocer mediante la función type():
>>> type(5) <class 'int'> >>> type(3.14) <class 'float'> >>> type("hola") <class 'str'> >>> type([3, 4]) <class 'list'>
Python crea los objetos a medida que se necesitan y los destruye cuando ya no necesitan (y ninguna variable hace referencia a ellos).
Cuando se destruye y crea un nuevo objeto, el identificador puede o no coincidir con identificadores utilizados anteriormente
En Windows, en el caso de los números enteros pequeños Python parece no destruir los objetos (o puede que les asigne el mismo identificador). En el ejemplo siguiente, se observa como los objetos "5" y "6" tienen siempre el mismo identificador.
>>> id(5) 505911024 >>> id(6) 505911040 >>> id(5) 505911024 >>> id(6) 505911040
Pero normalmente, los objetos se crean y se destruyen a medida que se usan y dejan de usar. En los ejemplos siguiente, se observa como los objetos (números grandes o cadenas) tienen cada vez un identificador distinto:
>>> id(12345) 50573840 >>> id(12346) 50576480 >>> id(12345) 50573856 >>> id(12346) 50573536
>>> id("hola") 50654720 >>> id("adios") 50654752 >>> id("hola") 50654656 >>> id("adios") 50654688
Si una variable hace referencia a un objeto, el objeto ya no destruye. En el ejemplo siguiente, se observa como el objeto "hola" mantiene el mismo identificador debido a que la variable "a" hace referencia a él.
>>> a = "hola" >>> id("hola") 50651296 >>> id("adios") 50654656 >>> id("hola") 50651296 >>> id("adios") 50654720 >>> id("hola") 50651296
En Python, las variables son simples referencias a los objetos. Una variable es una especie de etiqueta o de alias para referirnos al objeto.
En Python las variables también tienen identificador, tipo y valor, pero esas tres características son las del objeto al que hace referencia.
Cuando asignamos un valor a una variable, lo que estamos creando es una etiqueta para referirnos al objeto que contiene el dato.
Por ejemplo, si a una variable se le asigna el número 5, Python crea el objeto número 5 y la variable hace referencia a ese objeto, como se comprueba en el ejemplo siguiente:
>>> id(5) # Este es el identificador del objeto número entero "5" 505894368 >>> a = 5 >>> id(a) # El identificador de la variable a es el mismo que el del número entero "5" 505894368
Python crea el objeto "número 5" cuando ve que en el programa se va a utilizar un 5. En el ejemplo anterior se crearía el objeto y después se ejecutaría la función id () que obtiene el identificador del objeto. Si la primera instrucción hubiera sido la asignación de la variable, de la misma forma se hubiera creado primero el objeto "número 5" y después se le hubiera asociado la etiqueta "a" a dicho objeto.
Lógicamente, el tipo de una variable coincide con el tipo del objeto al que hace referencia la variable, como se comprueba en el ejemplo siguiente:
>>> type(5) # El tipo del objeto número entero "5" es int <class 'int'> >>> a = 5 >>> type(a) # El tipo de la variable a el mismo que el del objeto número entero "5" <class 'int'>
Cuando cambiamos el valor de una variable, en la mayoría de los casos la variable pasa simplemente de hacer referencia a un objeto a hacer referencia a otro (aunque en algunos casos se puede estar modificando el valor del objeto, como se verá más adelante en esta lección), como se comprueba en el ejemplo siguiente:
>>> id(1) 505894304 # Este es el identificador del objeto "1" >>> id(2) 505894320 # Este es el identificador del objeto "2" >>> a = 1 # Si la variable "a" se asigna al objeto "1" ... >>> id(a) # ... la variable "a" tiene el mismo identificador que el objeto "1" 505894304 >>> a = 2 # Pero si la variable "a" se asigna al objeto "2" ... >>> id(a) # ... la variable "a" tiene el mismo identificador que el objeto "2" 505894320 >>> b = a # Si la variable "b" se asigna al mismo objeto que la variable "a" ... >>> id(b) # ... la variable "b" tiene el mismo identificador que el objeto "2" 505894320
En Python algunos tipos de objetos son inmutables, pero otros objetos son mutables:
Como los objetos inmutables no se pueden modificar, al modificar las variables que hacen referencia a objetos inmutables, las variables pasan siempre a hacer referencia a otros objetos.
>>> id(5), id(7) (505894368, 505894400) # Estos son los identificadores de los objetos "5" y "7" >>> a = 5 # La etiqueta "a" se asigna al objeto "5" y ... >>> b = a # ... la etiqueta "b" se asigna al mismo objeto que "a" >>> a, b # Tanto "a" como "b" valen 5 ... (5, 5) >>> id(a), id(b) # ... porque a y b tienen el mismo identificador que "5" (505894368, 505894368) >>> b = b + 2 # Pero si aumentamos el valor de b en dos unidades ... >>> a, b # ... "a" sigue valiendo 5 pero "b" vale 7 (5, 7) >>> id(a), id(b) # La variable "b" hace ahora referencia al objeto "7" (505894368, 505894400)
En este ejemplo, cuando modificamos el valor de la variable b ...
>>> b = b + 2 # Pero si "aumentamos" el valor de b en dos unidades ...
... en realidad Python no aumenta el valor de "b" sino que:
>>> id("hola"), id("hola y adios") (47477152, 42474696) >>> a = "hola" >>> b = a >>> a, b ('hola', 'hola') >>> id(a), id(b) (47477152, 47477152) >>> b = b + " y adios" >>> a, b ('hola', 'hola y adios') >>> id(a), id(b) (47477152, 47475752)
En este ejemplo podemos observar que el id de la primera cadena "hola y adios" (aquella cuyo identificador se solicita en la primera instrucción) no es el mismo que el de la que aparece más adelante (al añadirle " y adios" a "hola"), pero eso se debe a que Python ha destruido y vuelto a crear el objeto entre medias.
Sin embargo, en el caso de los objetos mutables tenemos dos posibilidades: modificar las variables o modificar los objetos mutables a los que hacen referencia. Por ejemplo, en el caso de las listas:
>>> a = [5] # La variable "a" se asigna al objeto "lista [5]" y ... >>> b = a # ... la variable "b" se asigna al mismo objeto que "a" ... >>> id(a), id(b) # ... por eso "a" y "b" tienen el mismo identificador (45345888, 45345888) >>> b = b + [6] # Pero si añadimos un elemento a "b" ... >>> id(a), id(b) # ... la variable "b" hace ahora referencia al objeto "[5, 6]" ... (45345888, 45346248) >>> a, b # ... y por eso "a" y "b" son distintos. ([5], [5, 6])
En este ejemplo, cuando modificamos el valor de la variable b ...
>>> b = b + [6] # Pero si añadimos un elemento a "b" ...
... en realidad Python no añade un elemento a "b" sino que:
>>> a = [5] # La variable "a" identifica a la lista [5] ... >>> b = a # ... la variable "b" identifica al mismo valor que a... >>> id(a), id(b) # ... por eso "a" y "b" tienen el mismo identificador (46531608, 46531608) >>> b += [6] # Pero si añadimos un elemento a b usando += ... >>> id(a), id(b) # ... tanto "a" como "b" siguen haciendo referencia a la misma lista ... (46531608, 46531608) >>> a, b # ... que ha cambiado con respecto a su valor inicial ([5, 6], [5, 6])
En este ejemplo, cuando modificamos el valor de la variable b ...
>>> b += [6] # Pero si añadimos un elemento a b usando += ...
... en realidad Python:
Lo mismo pasaría si se utilizara el método append():
>>> a = [5] # La variable "a" identifica a la lista [5] ... >>> b = a # ... la variable "b" identifica al mismo valor que a... >>> id(a), id(b) # ... por eso "a" y "b" tienen el mismo identificador (46531608, 46531608) >>> b.append(6) # Pero si añadimos un elemento a b usando append() ... >>> id(a), id(b) # ... tanto "a" como "b" siguen haciendo referencia a la misma lista ... (46531608, 46531608) >>> a, b # ... que ha cambiado con respecto a su valor inicial ([5, 6], [5, 6])
>>> a = [5] # Al crear dos variables con la misma lista ... >>> b = [5] >>> id(a), id(b) # ... Python crea dos objetos y cada variable hace referencia a uno de ellos. (45346248, 46530272)
Por ejemplo, en el caso de los números enteros pequeños (o cadenas) sólo crea un objeto:
>>> a = 5 # Al crear dos variables que hacen referencia al mismo valor ... >>> b = 5 >>> id(a), id(b) # ... Python crea un sólo objeto (505894368, 505894368)
Pero en el caso de los decimales, crea objetos distintos.
>>> a = 3.14 # Al crear dos variables que hacen referencia al mismo valor ... >>> b = 3.14 >>> id(a), id(b) # ... Python crea dos objetos (46981696, 46981744)
>>> id(3), id(4) # Al crear una tupla de dos identificadores de números ... (505894336, 505894352) # ... Python crea los dos objetos. >>> id([3]), id([4]) # Pero al crear una tupla de dos identificadores de listas ... (46530272, 46530272) # ... Python parece crear un único objeto.
Lo que ocurre es que como el objeto "lista [3]" no se utiliza para nada más (no hay ninguna variable haciendo referencia a él), nada más crearlo se destruye. Al crear el objeto "lista [4]", Python le da el mismo identificador que había utilizado antes.
Nota: Obtuve esta aclaración haciendo una consulta en la lista comp.lang.python.
>>> a = (3, 5) >>> a (3, 5) >>> a[0] = 4 Traceback (most recent call last): File "<pyshell#45>", line 1, in <module> a[0] = 3 TypeError: 'tuple' object does not support item assignment >>> a (3, 5)
Pero si una tupla incluye objetos mutables, aunque se produzca un error por intentar un objeto inmutable, el objeto mutable sí que se modifica:
>>> a = ([3], [5]) >>> a ([3], [5]) >>> a[0] += [4] Traceback (most recent call last): File "<pyshell#45>", line 1, in <module> a[0] += [3] TypeError: 'tuple' object does not support item assignment >>> a ([3, 4], [5])
La explicación es que el objeto tupla está formado por dos objetos listas y Python primero modifica el objeto lista (lo que está permitido) pero después detecta que se ha intentado modificar el objeto tupla (lo que no está permitido).
Para modificar el objeto mutable sin generar mensajes de error, debemos utilizar el método append():
>>> a = ([3], [5]) >>> a ([3], [5]) >>> a[0].append(4) >>> a ([3, 4], [5])