
Programaci贸n Orientada a Objetos (POO) en Python

¡Hola! Cuando se publique este post puede que muchos estéis de vacaciones (disfrutad, ya me tocará a mí 馃槇) pero en cosasdedevs no paramos en agosto y, ya que no paramos toca nuevo tutorial como todas las semanas 馃帀. Esta vez toca hablar de la POO en PYTHON.
¿Qué es la Programación Orientada a Objetos (POO)?
Para el que no lo sepa, la POO como bien dice la Wikipedia, es un paradigma de programación que viene a innovar la forma de obtener resultados. Los objetos manipulan los datos de entrada para la obtención de datos de salida específicos, donde cada objeto ofrece una funcionalidad especial.
¿Esto que quiere decir?
Bien, nosotros al crear un objeto, podremos pasar unos parámetros de configuración al inicializarlo o en métodos del objeto y este podrá devolvernos uno o varios resultados según nuestras necesidades.
Una vez aclarado esto, vamos a ver como crear una clase y posteriormente objeto en Python.
Clases en Python
Para definir una clase en Python lo haremos de la siguiente forma:
class PrimeraClase:
pass
Solo necesitamos escribir class y el nombre que le queremos dar a la clase que por convención es CamelCase. Por último para que no falle, ya que no hemos añadido ninguna función ni variable, añadimos pass (una operación nula).
Ahora vamos a darle más funcionalidad a nuestra clase. También podemos añadir constructores y destructores como en otros lenguajes con __init__ como constructor y __del__ para el destructor:
class PrimeraClase:
def __init__(self):
print('Estoy vivo')
def __del__(self):
print('Este es mi fin')
Observad que tanto para el __init__ como el __del__ estamos pasando el parámetro self, este parámetro también conocido como this en otros lenguajes se debe pasar obligatoriamente como primer parámetro en todos los métodos que creemos dentro de la clase y lo utilizaremos para acceder a todos los atributos de la clase.
Crear un objeto
Crear un objeto en Python es muy sencillo, solamente tenemos que llamar a la clase para crear el objeto y listo:
pc = PrimeraClase()
Al hacer esto en nuestro ejemplo, se llamaría al constructor, lo que haría mostrar el mensaje 'Estoy vivo', al finalizar la ejecución del script se llamaría al destructor y veríamos el mensaje 'Este es mi fin'.
Si nuestra clase tuviera parámetros, solo tendríamos que pasarlos al crear el objeto:
pc = PrimeraClase(param1, param2, 'test')
Ahora que hemos visto el primer ejemplo es hora de profundizar en la POO con Python 馃殌.
Abstracción
La abstracción cuando queremos trabajar con POO digamos que sería descomponer el objeto que queremos crear para quedarnos con la información relevante y separar la información importante de los detalles secundarios. Para realizar esto podemos utilizar variables y métodos privados y públicos.
¿Como declarar métodos/variables privados y protegidos en Python?
Los métodos y variables en Python siempre serán públicos pero existen una serie de convenciones para hacerlos privados. Para declarar métodos o variables privados/protegidos lo haremos añadiendo al principio de la definición del método/variable un guion bajo (_) para los protegidos y para los privados con dos guiones (__):
class SegundaClase:
_variable_protegida = 1
__variable_privada = 2
if __name__ == "__main__":
sc = SegundaClase()
print(sc._variable_protegida)
print(sc.__variable_privada)
Si intentamos acceder a la primera variable lo podremos hacer sin problema, sin embargo en la segunda se mostrará un error. Esto es porque el intérprete de Python al leer una variable o método con __ lo renombra para evitar colisiones con subclases añadiendo al inicio un guion y el nombre de clase. Para hacer la "trampa" y acceder, tendremos hacerlo de la siguiente forma:
class SegundaClase:
_variable_protegida = 1
__variable_privada = 2
if __name__ == "__main__":
sc = SegundaClase()
print(sc._variable_protegida)
print(sc._SegundaClase__variable_privada)
Una vez aclarado esto, vamos a explicar que es la encapsulación y como controlar el acceso a nuestras variables privadas o protegidas.
Encapsulación
La encapsulación nos permite agrupar datos y controlar su comportamiento en nuestra clase. También nos permite controlar el acceso a nuestros datos y prevenir modificaciones no autorizadas.
Si por ejemplo, queremos controlar el acceso a una de nuestras variables o realizar una acción adicional al intentar acceder a un dato lo podremos hacer con los getters y setters.
Getters y Setters en Python
Los getters serían las funciones que nos permiten acceder a una variable privada. En Python se declaran creando una función con el decorador @property.
Los setters serían las funciones que usamos para sobreescribir la información de una variable y se generan definiendo un método con el nombre de la variable sin guiones y utilizando como decorador el nombre de la variable sin guiones más ".setter".
Y para dejarlo claro del todo, vamos a verlo con el siguiente ejemplo:
class ListadoBebidas:
def __init__(self):
self._bebida = 'Test cola'
self._bebidas_validas = ['Test cola', 'Cerveza']
@property
def bebida(self):
return f'La bebida oficial es {self._bebida}'
@bebida.setter
def bebida(self, bebida):
if bebida in self._bebidas_validas:
self._bebida = bebida
else:
raise ValueError(f'La bebida {bebida} no est谩 en el listado de bebidas v谩lidas')
if __name__ == "__main__":
bebidas = ListadoBebidas()
print(bebidas.bebida)
bebidas.bebida = 'Limonada'
En este ejemplo declaramos dos variables, una llamada _bebida y una lista llamada _bebidas_validas. Para recuperar la información de la variable _bebida tendremos que hacerlo con el objeto y el nombre de la función bebida. Como veis en el ejemplo, podemos manipular el resultado para mostrar la información como queramos.
Para modificar el valor de la variable, lo haremos de la siguiente forma bebidas.bebida = 'Limonada', esto llamará el setter y se ejecutará la función. En nuestro caso se lanzará una excepción, ya que la bebida que pasamos no está en el listado de bebidas válidas.
Herencia
La herencia nos permite generar una jerarquía de clases en las que podemos compartir funcionamientos comunes y en el que existirá una clase padre también conocida como superclase y una o varias clases hijas conocidas como subclases.
Para extender de una clase padre en Python solo tendremos que pasar como parámetro el nombre de la clase padre a la hija en su definición y ya podremos usar las funcionalidades de la clase padre.
Vamos a verlo con un ejemplo:
class Rectangulo:
def __init__(self, base, altura):
self.base = base
self.altura = altura
def area(self):
return self.base * self.altura
class Cuadrado(Rectangulo):
def __init__(self, lado):
super().__init__(lado, lado)
if __name__ == '__main__':
rectangulo = Rectangulo(3, 4)
print(rectangulo.area())
cuadrado = Cuadrado(5)
print(cuadrado.area())
Para este ejemplo hemos creado una clase padre llamada Rectángulo, esta recibirá en el constructor la base y la altura y cuando llamemos al método área podremos recuperar el área del rectángulo.
Para realizar la herencia, hemos creado una clase hija llamada Cuadrado que extenderá de la clase Rectángulo como se ve en el ejemplo. Ya que para calcular el área del cuadrado solo necesitamos un valor, en este caso sobreescribimos el constructor esta vez enviando un solo parámetro y luego utilizamos super().__init__ para usar el constructor de la clase padre y así guardar la base y la altura que en este caso serán el mismo valor.
Una vez hecho esto no haría falta hacer nada más. Podríamos llamar al método área desde el objeto cuadrado y funcionaría perfectamente.
Polimorfismo
El polimorfismo en la POO nos permite modificar el comportamiento de una superclase para adaptarlo a las necesidades de una subclase. Esto nos ayudará a crear una clase general con unas definiciones por defecto que luego podremos ir adaptando según las necesidades de la clase hija. Ejemplo:
class Vehiculo:
def __init__(self, nombre):
self.nombre = nombre
def num_ruedas(self):
pass
class Motocicleta(Vehiculo):
def __init__(self):
super().__init__('Motocicleta')
def num_ruedas(self):
return 2
if __name__ == "__main__":
moto = Motocicleta()
print(moto.num_ruedas())
En este caso tenemos una clase padre Vehículo con un método num_ruedas y una clase hija Motocicleta que heredará de Vehículo. Ya que según el vehículo que estemos creando tendrá un número de ruedas u otro, sobreescribimos el método num_ruedas para dar el resultado que buscamos.
Conclusiones
Si ya habíais visto POO en otros lenguajes, veréis que al final no difiere mucho de un lenguaje a otro, si es la primera vez que lo veis y aún tenéis dudas, podéis escribirlas en los comentarios que estaré encantando de ayudaros 馃槈.
Espero que este post te ayude y como siempre, te recomiendo seguirme en Twitter para estar al tanto de los nuevo contenido. Ahora también puedes seguirme en Instagram donde estoy subiendo tips, tutoriales en vídeo e información sobre herramientas para developers.
Por último os dejo mi guía para aprender a trabajar con APIs donde explico todo el funcionamiento de una API, el protocolo HTTP y veremos como construir una API con arquitectura REST.
Nos leemos 馃憢.