logo cosasdedevs
Programación Orientada a Objetos (POO) en Python

Programación Orientada a Objetos (POO) en Python



My Profile
Ago 13, 2020

¡Hola! En el tutorial de esta semana vas a aprender como emplear la programación orientada a objetos en Python, veremos la creación de clases, abstracción, encapsulación, polimorfismo y herencia. Let's go!

Este tutorial ha sido reescrito el 29/05/2022.

¿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, cuando nosotros creamos una instancia de una clase (objeto), podemos enviar parámetros de configuración al crearlo o en métodos del objeto y este podrá devolvernos uno o varios resultados según nuestra configuración.

¿Qué son los métodos?

Los métodos son las funciones que generamos dentro de una clase.

Clases en Python

Para definir una clase en Python lo haremos de la siguiente forma:

class Saludo:
    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 la palabra reservada pass que significa que es una operación nula y evita que falle nuestra clase.

Ahora vamos a darle más funcionalidad a nuestra clase. También podemos añadir constructores y destructores al igual que en otros lenguajes con __init__ como constructor y __del__ para el destructor:

class Saludo:

    def __init__(self):
        print('Me acaban de instanciar 🤘')

    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 y métodos de la clase.

Crear un objeto

Para crear un objeto en Python, solo tenemos que llamar a la clase para crear el objeto y listo:

saludar = Saludo()

Al hacer esto en nuestro ejemplo, se llamaría al constructor, lo que haría mostrar el mensaje 'Me acaban de instanciar 🤘', al finalizar la ejecución del script se llamaría al destructor y veríamos el mensaje 'Este es mi fin'.

Atributos

Los atributos son las variables que contienen nuestras clases. Por ejemplo, en nuestra clase Saludo, podemos definir un atributo "nombre". Para inicializarlo, podemos recibir el valor por el constructor y luego emplear ese atributo en cualquier parte de la clase.

class Saludo:

    nombre: str
    def __init__(self, nombre):
        self.nombre = nombre
        print('Me acaban de instanciar 🤘')

    def __del__(self):
        print('Este es mi fin')

    def saludar(self):
        print(f'Hola {self.nombre}')


saludo = Saludo('Alber')
saludo.saludar()

Como puedes ver en el ejemplo, definimos el atributo nombre, después recibimos un parámetro nombre por el constructor que usamos para inicializar nuestro atributo. También he generado un método llamado saludar que imprimirá un saludo junto al nombre introducido al instanciar la clase.

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 emplear 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 Saludo:

    _nombre: str
    __apellido: str
    def __init__(self, nombre: str, apellido: str):
        self._nombre = nombre
        self.__apellido = apellido
        print('Me acaban de instanciar 🤘')

    def __del__(self):
        print('Este es mi fin')

    def saludar(self):
        print(f'Hola {self._nombre} {self.__apellido}')


saludo = Saludo('Alber', 'Ramírez')
print(saludo._nombre)
print(saludo.__apellido)

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 Saludo:

    _nombre: str
    __apellido: str
    def __init__(self, nombre: str, apellido: str):
        self._nombre = nombre
        self.__apellido = apellido
        print('Me acaban de instanciar 🤘')

    def __del__(self):
        print('Este es mi fin')

    def saludar(self):
        print(f'Hola {self._nombre} {self.__apellido}')


saludo = Saludo('Alber', 'Ramírez')
print(saludo._nombre)
print(saludo._Saludo__apellido)

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 ejecutar 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 son las funciones que nos permiten acceder a una variable privada. En Python se declaran creando una función con el decorador @property.

Los setters son 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 generado una clase hija llamada Cuadrado que extenderá de la clase Rectángulo, como se ve en el ejemplo. Cuando queremos hacer que una clase extienda de otra, después de indicar el nombre de clase, debemos añadir entre paréntesis la clase de la que queremos que extienda.

Ya que para calcular el área del cuadrado únicamente necesitamos un valor, en este caso sobreescribimos el constructor esta vez enviando un solamente parámetro y en el constructor tenemos la siguiente línea:

super().__init__(lado, lado)

Lo primero que puedes ver es la función "super()". Esta función nos permite invocar un método o atributo de la clase padre que en este caso es "Rectangulo". El método que necesitamos llamar es el constructor, ya que lo necesitamos para inicializar los atributos base y altura y, puesto que en un cuadrado tienen el mismo valor, enviamos dos veces el mismo valor lado.

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.

La herencia en Python también nos permite heredar de múltiples clases a la vez. Para ello solo debemos separarlas por comas:

class Nombre:
    _nombre: str

class Apellido:
    _apellido: str

class Saludo(Nombre, Apellido):
    def __init__(self, nombre: str, apellido: str):
        self._nombre = nombre
        self._apellido = apellido

    def saludar(self):
        print(f'Hola {self._nombre} {self._apellido}')


saludo = Saludo('Alber', 'Ramírez')
saludo.saludar()

Como puedes observar, tenemos la clase Nombre y Apellido que tienen los atributos protegidos _nombre y _apellido respectivamente. En la clase Saludo después de declarar su nombre, solo debemos añadir las clases de las que queramos que herede Saludo separado por comas y entre paréntesis y ya podremos usar sus atributos.

Polimorfismo

El polimorfismo en la POO nos permite modificar el comportamiento de una clase padre para adaptarlo a las necesidades de una clase hija. 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 existes varios tipos de vehículos que difieren en el número de ruedas, lo definimos sin ningún valor. Cuando creamos la clase Motocicleta que extiende de Vehículo, sobreescribimos el método num_ruedas para dar el resultado que buscamos.

Conclusiones

Si ya has visto POO en otros lenguajes, verás 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 👋.

7546 vistas

🐍 Sígueme en Twitter

Si te gusta el contenido que subo y no quieres perderte nada, sígueme en Twitter y te avisaré cada vez que cree contenido nuevo 💪
Luego ¡Te sigo!

Nos tomamos en serio tu privacidad

Utilizamos cookies propias y de terceros para mejorar la experiencia del usuario a través de su navegación. Si pulsas entendido aceptas su uso. Ver política de cookies.