Referencias en Python:
Contents
Referencias en Python:#
Important
¿Cómo vamos?
Hasta el momento debemos tener claro lo siguiente:
Note
Apuntadores
En lenguajes como C o C++ los apuntadores son unas herramientas destacadas en la optimización de rutinas puesto que indican la dirección de memoria donde se guardan las variables. En ese sentido, en tareas como la asignación dinámica de memoria, la creación de código conciso y compacto (sin reescribir encabezados) y en la eficiencia del código; los apuntadores son fundamentales para los programadores en C o C++. Si quieres más información consulta en https://www.tutorialspoint.com/cprogramming/c_pointers.htm
En Python no hay apuntadores, todo por mantener los 20 principios de Zen de Python. Recordemos:
import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
En este particular, la implementación de apuntadores contradice varios de los principios del lenguaje, ya bien se por su complejidad, por hacer cambios implicitos y porque esencialmente Python se concentra en la usabilidad más que en la velocidad.
No quiere decir lo anterior que Python impida optimizar código con algunas ideas provenientes del uso de apuntadores, hay algunos beneficios de estas ideas que se pueden implementar en Python. Veamos en detalle lo que significa guardar un valor en Python.
¡Todo es objeto!#
En el estudio de paradigmas de programación vimos que hay un estilo de programación orientado a objetos, en ese resumen vimos que el concepto de objeto que se entiende como una entidad abstracta conformado por métodos y atributos. Veamos lo siguiente:
isinstance(1,int)
True
isinstance()
permite identificar si un objeto es una instancia de una clase. Aquí ya estamos diciendo que el entero es un objeto, como si quisieramos decir que implicitamente todo lo que clasifique isinstance es un objeto, y pues :
isinstance(1,object)
True
Claramente si pensamos en más y más elementos tendriamos un resultado similar:
import math
print(isinstance(math.sqrt,object))
print(isinstance(math.pi,object))
print(isinstance([1,2,3],object))
print(isinstance({1:1,2:2,3:'Hola'},object))
True
True
True
True
Estos objetos en Python tienen una estructura interesante, se tratan, la mayoría de veces, de la misma manera por el lenguaje y siempre cuentan con dos partes importantes:
Conteo de referencias, para hacer gestión de memoria, ver https://realpython.com/python-memory-management/ y
Tipo que determina qué tipo de objeto es (por ejemplo, un número entero, una lista o una función definida por el usuario).
Diferencias de las variables entre C y Python#
En el caso de C
Si hacemos lo siguiente:
int x=152
estamos siguiendo el siguiente proceso:
Lo que ocurre es que se asigna un espaciode memoria para un número entero, el valor 152 se almacena en ese espacio de memoria y se indica que x
apunta a ese valor.
Si cambiamos el valor de la variable,
x=898
entonces cambiamos el valor guardado en ese espacio de memoria:
En el caso de Python
El ejercicio análogo hace un proceso un poco más largo. Crea un PyObject
, identifica que el tipo de ese elemento es entero indica que ese objeto tiene valor 152, por otro lado crea un nombre llamado x
, apunta x
a PyObject
y aumenta el contador de referencias del PyObject
en 1:
Observe que la memoria guarda el objeto 152, en este caso el nombre x
no tiene dirección de memoria, si hacemos una actualización como x=153
ocurre lo siguiente:
Si hacemos algo más interesante, como por ejemplo y= x
Note que el contador de referencias se va actualizando cada vez que se hace una asignación nueva.
Objetos mutables vs inmutables#
Recuerdas la gran diferencia de la lista y la tupla, la lista se puede modificar cuando se quiera, en la tupla no puedo hacer eso, por ejemplo:
a=(1,2,3)
b=[1,2,3]
b[0]=2
print(b)
[2, 2, 3]
a[0]=2
print(a)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Input In [7], in <cell line: 1>()
----> 1 a[0]=2
2 print(a)
TypeError: 'tuple' object does not support item assignment
Pasa muy parecido con las cadenas de caracteres:
txt='hola'
txt[0]='b'
txt
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/tmp/ipykernel_7868/3685992916.py in <module>
1 txt='hola'
----> 2 txt[0]='b'
3 txt
TypeError: 'str' object does not support item assignment
En particular tenemos que:
Tipo |
¿Inmutable? |
---|---|
|
Sí |
|
Sí |
|
Sí |
|
Sí |
|
Sí |
|
Sí |
|
Sí |
|
No |
|
No |
|
No |
Ahora bien, en Python, el usuario relaciona el objeto y su contenido mediante una referencia, la cual es a la dirección en la cual se encuentra alojado dicho objeto en la memoria del computador (apuntador) mediante la función id()
, así:
id(1)
140227037911344
En una asignación como la siguiente:
x = 1
x
no contiene el valor 1, pero sí la información del objeto en la memoria, en el cual se se encuentra alojado tal valor. En otras palabras, podríamos afirmar que x
“apunta” o “se refiere” a un objeto en el cual se halla alojado el 1.
x=1
id(x)
140227037911344
Observe que las dos salidas coinciden. Algo que debemos tener claro es que dos objetos diferentes no pueden estar alojados en la misma dirección, es decir, cada objeto tiene una única dirección en la memoria. No nos es posible acceder a la dirección de los objetos, pero podemos acceder al identificador único que ellos poseen:
print(id(1))
print(id(2))
140227037911344
140227037911376
Muy probablemente, este número entero que obtuvimos, conocido como la identidad del objeto, sea diferente en cada máquina y en cada oportunidad que reiniciemos el kernel. El siguiente es un ejempo muy claro en el cual podremos ver que la identidad de cada objeto es única:
def cuadrado(x):
print('El valor es:',x,'y su identidad es:',id(x))
print('Su cuadrado es:',x**2,'y su identidad es:',id(x**2))
return x**2
cuadrado(1)
El valor es: 1 y su identidad es: 140227037911344
Su cuadrado es: 1 y su identidad es: 140227037911344
1
cuadrado(x)
El valor es: 1 y su identidad es: 140227037911344
Su cuadrado es: 1 y su identidad es: 140227037911344
1
Notemos que la función cuadrado
regresa el mismo valor de identidad para 1, su cuadrado, \(x\) y su cuadrado, ya que en ambos casos hablamos del mismo objeto, mientras que para el siguiente caso el valor de la identidad del parámetro y de su cuadrado cambian:
Si hay un cambio:
cuadrado(2)
El valor es: 2 y su identidad es: 140227037911376
Su cuadrado es: 4 y su identidad es: 140227037911440
4
Para comprobar que dos elementos tienen la misma identidad empleamos la función is
, así:
x is 1
True
2 is x+1
True
Notemos que, la función puede recibir la referencia a un objeto inmutable, y por más cálculos que se hagan sobre dicho objeto, éste no estará afectado, por ejemplo:
y = 2
def cuadrado(x):
print('El valor es:',x,'su identidad es:',id(x))
x = x**2
print('Su cuadrado es:',x,'y su identidad es:',id(x))
return y
cuadrado(y)
El valor es: 2 su identidad es: 140227037911376
Su cuadrado es: 4 y su identidad es: 140227037911440
2
y
2
Cabe resaltar que si no hacemos uso de la referencia del valor original, se estaría almacenando un poco de basura en nuestra memoria.
En el caso de los objetos mutables pasa algo muy curioso, veamos:
Lista=[1,2,3]
print(Lista)
print(id(Lista))
[1, 2, 3]
140226659470528
Lista[1]=0
print(Lista)
print(id(Lista))
[1, 0, 3]
140226659470528
Estos objetos cambian no solo en Python sino que en la memoria también, a diferencia de los elementos inmutables que asumen cambios en nuevos espacios de memoria, los elementos mutables cambian en ese mismo espacio.
Cierre#
Python tiene una filosofia interesante frente al uso de memoria, cambia totalmente la idea detrás de los apuntadores y utiliza nombres más allá que variables. Si quisiese aplicar ideas de optimización con apuntadores en este lenguaje debería trabajar con objetos mutables, sin embargo, recuerda:
import this