Introducción a la Programación
Para entender bien los objetos debemos tener claras dos cuestiones fundamentales:
¿Cuándo y dónde existen los objetos?
Es importante tener claro que los objetos "existen" sólo durante la ejecución del programa y se almacenan en la memoria del sistema operativo.
Es decir, mientras las clases están ahí en el código haciendo su papel de instrucciones, los objetos no existen hasta que el programa se ejecuta y se crean en la memoria.
Este proceso de "crear" los objetos en la memoria se denomina instanciación y para realizarlo es tan fácil como llamar a la clase como si fuera una función:
una_masita = Masita()
otra_masita = Masita()
Demostrar que las masitas existen como "entes independientes" dentro de la memoria, es tan sencillo como imprimirlas por pantalla:
print(una_masita)
print(otra_masita)
<__main__.Masita object at 0x000001433B4C2048>
<__main__.Masita object at 0x000001433B439FD0>
Cada instancia tiene su propia referencia, demostrando que están en lugares distintos de la memoria. En cambio, la clase no tiene una referencia porque es sólo un guion de instrucciones:
print(Masita)
<class '__main__.Masita'>
Es posible consultar la clase de un objeto con la función type(), pero también se puede consultar a través de su atributo especial class:
print(Masita)
print(type(una_masita))
print(una_masita.__class__)
<class '__main__.Masita'>
<class '__main__.Masita'>
<class '__main__.Masita'>
A su vez las clases tienen un atributo especial name que nos devuelve su nombre en forma de cadena sin adornos:
print(Masita.__name__)
print(type(una_masita).__name__)
print(una_masita.__class__.__name__)
Masita
Masita
Masita
Resumiendo: los objetos son instancias de una clase.
Atributos y métodos
Como ya vimos que algo que ilustra el potencial de la POO (Programación Orientada a Objetos) esa es la capacidad de definir variables y funciones dentro de las clases, aunque aquí se conoce como atributos y métodos respectivamente.
Atributos
A efectos prácticos los atributos no son muy distintos de las variables, la diferencia fundamental es que sólo existen dentro del objeto.
Atributos dinámicos
Dado que Python es muy flexible los atributos pueden manejarse de distintas formas, por ejemplo se pueden crear dinámicamente (al vuelo) en los objetos.
classMasita:
pass
masita=Masita()
masita.sabor="salado"
masita.color="marrón"
print(f"El sabor de esta masita es {masita.sabor} " f"y el color {masita.color}") El sabor de esta masita es Salado y el color Marrón
Atributos de clase
Aunque la flexibilidad de los atributos dinámicos puede llegar a ser muy útil, tener que definir los atributos de esa forma es tedioso. Es más práctico definir unos atributos básicos en la clase. De esa manera todas las masitas podrían tener unos atributos por defecto:
classMasita:
chocolate=False
masita=Masita()
ifmasita.chocolate:
print("La masita tiene chocolate")
else:print("La masita no tiene chocolate")
La masita no tiene chocolate
Luego podemos cambiar su valor en cualquier momento:
masita.chocolate=True
ifmasita.chocolate:
print("La masita tiene chocolate")
else:print("La masita no tiene chocolate")
La masita tiene chocolate
Por lo menos de esta forma nos aseguraremos de que el atributo chocolate existe en todas las masitas desde el principio. Además, es posible consultar el valor por defecto que deben tener las masitas haciendo referencia al atributo en la definición de la clase:
print(Masita.chocolate)False
Lo curioso es que si cambiamos ese atributo de clase (que no de objeto) a True, las siguientes masitas se crearán con chocolate, es decir, habremos modificado las instrucciones de creación de los objetos:
classMasita:
chocolate=False
Masita.chocolate=True
masita=Masita()
ifmasita.chocolate:
print("La masita tiene chocolate")
else:print("La masita no tiene chocolate")
La masita tiene chocolate
Métodos
Si por un lado tenemos las "variables" de las clases, por otro tenemos sus "funciones", que evidentemente nos permiten definir funcionalidades para llamarlas desde las instancias.
Definir un método es bastante simple, sólo tenemos que añadirlo en la clase y luego llamarlo desde el objeto con los paréntesis, como si de una función se tratase:
classMasita:
chocolate=False
defsaludar():
print("Hola, soy una masita muy sabrosa")
masita=Masita()
masita.saludar()
----------------------------------------------------------------------TypeError Traceback (most recent call last)<ipython-input-27-74df06911b9b> in <module>() 6 7 masita = Masita()----> 8 masita.saludar() TypeError: saludar() takes 0 positional arguments but 1 was given
Sin embargo, al intentar ejecutar el código anterior desde una masita veréis que no funciona. Nos indica que el método saludar() requiere 0 argumentos pero se está pasando uno.
¿Cómo puede ser? Si en ningún momento hemos enviado ninguna información a masita...
Lo que tenemos aquí es la diferencia fundamental entre métodos de clase y métodos de instancia.
Probad a ejecutar el método llamando a la clase en lugar del objeto:
classMasita:
chocolate=False
defsaludar():
print("Hola, soy una masita muy sabrosa")
Masita.saludar()Hola, soy una masita muy sabrosa¡Ahora sí ha funcionado! ¿Cómo es posible? Y más importante, ¿por qué al llamarlo desde el objeto dice que estamos enviando un argumento? Veamos este tema en la a continuación.