PROGRAMACIÓN ORIENTADA
A OBJETOS EN PYTHON
SENA Centro Agropecuario Buga
Junio 06 2025
Programación Orientada a Objetos en Python | Omar Acosta Torres
Tema 1: Clases y Objetos
1.1. Explicación
La Programación Orientada a Objetos (POO) en Python se basa en dos conceptos
fundamentales: Clases y Objetos.
• Clase: Piensa en una clase como un "plano" o una "plantilla" para crear algo. No es el
objeto en sí, sino la definición de cómo debe ser. Define las características (atributos)
y los comportamientos (métodos) que tendrán los objetos creados a partir de ella. Por
ejemplo, la clase Coche podría definir que un coche tiene color, marca, modelo
(atributos) y puede arrancar, frenar, acelerar (métodos).
• Objeto (Instancia): Un objeto es una realización concreta de una clase. Es decir, es
una entidad individual creada a partir del "plano" de la clase. Siguiendo el ejemplo
anterior, si Coche es la clase, entonces un Ford Focus rojo 2020 o un Toyota Corolla
azul 2023 serían objetos (o instancias) de la clase Coche. Cada objeto tiene sus
propios valores para los atributos definidos en la clase.
1.2. Ejemplos
Ejemplo 1: Definiendo una Clase Simple
Python
Programación Orientada a Objetos en Python | Omar Acosta Torres
Explicación del ejemplo:
• class Perro:: Declara una nueva clase llamada Perro.
• raza = "Caniche": Este es un atributo de clase. Significa que todas las instancias de
Perro compartirán el valor "Caniche" para raza por defecto.
• def ladrar(self):: Define un método llamado ladrar. El parámetro self es crucial en
Python; se refiere a la instancia actual del objeto y permite acceder a sus atributos y
otros métodos.
• mi_perro = Perro(): Crea un nuevo objeto (instancia) de la clase Perro y lo asigna a la
variable mi_perro.
• mi_perro.ladrar(): Llama al método ladrar del objeto mi_perro.
Ejemplo 2: Clases con Atributos de Instancia y el Constructor __init__
En el ejemplo anterior, raza era un atributo de clase. Pero, ¿qué pasa si queremos que cada
perro tenga un nombre y una edad diferentes? Para eso usamos los atributos de instancia y el
método especial __init__.
Python
Programación Orientada a Objetos en Python | Omar Acosta Torres
Explicación del ejemplo:
• def __init__(self, nombre, edad):: Este es el constructor de la clase. Es un método
especial que se ejecuta automáticamente cuando se crea una nueva instancia de
Perro.
o self: Siempre es el primer parámetro y se refiere a la instancia que se está
creando.
o nombre, edad: Son parámetros que se pasan cuando se crea un objeto (ej.
Perro("Max", 3)).
• [Link] = nombre: Crea un atributo de instancia llamado nombre para el objeto
actual (self) y le asigna el valor del parámetro nombre. Lo mismo ocurre con
[Link].
• Ahora, cuando creamos perro1 = Perro("Max", 3), Max y 3 se asignan a [Link]
y [Link] respectivamente. perro2 tendrá sus propios valores.
• [Link] dentro de ladrar() y presentarse() permite que el método acceda al
atributo nombre específico de la instancia que lo está llamando.
1.3. Taller Propuesto
Taller 1: Gestión de Libros
Objetivo: Crear una clase Libro para representar información sobre libros y luego crear
objetos de esa clase.
Instrucciones:
1. Define una clase llamada Libro que tenga los siguientes atributos de instancia:
o titulo (cadena de texto)
o autor (cadena de texto)
o anio_publicacion (entero)
o disponible (booleano, True por defecto al crear un libro)
2. Define un método constructor (__init__) para la clase Libro que reciba titulo, autor y
anio_publicacion como parámetros. El atributo disponible debe inicializarse en True.
3. Define los siguientes métodos para la clase Libro:
o mostrar_info(self): Imprime en consola todos los detalles del libro (título,
autor, año de publicación y disponibilidad).
o prestar_libro(self): Si el libro está disponible, cambia su estado a False e
imprime un mensaje indicando que el libro ha sido prestado. Si no está
disponible, imprime un mensaje informando que no se puede prestar.
Programación Orientada a Objetos en Python | Omar Acosta Torres
o devolver_libro(self): Si el libro no está disponible, cambia su estado a True e
imprime un mensaje indicando que el libro ha sido devuelto. Si ya está
disponible, imprime un mensaje informando que no se puede devolver porque
ya está en la biblioteca.
4. Crea al menos tres objetos (instancias) de la clase Libro, con información diferente
para cada uno.
5. Realiza las siguientes operaciones con tus objetos:
o Muestra la información de todos los libros creados.
o Intenta prestar un libro que esté disponible y luego muestra su información.
o Intenta prestar el mismo libro nuevamente (debería decir que no está
disponible).
o Devuelve el libro que prestaste y luego muestra su información.
o Intenta devolver un libro que nunca fue prestado (debería decir que ya está en
la biblioteca).
Programación Orientada a Objetos en Python | Omar Acosta Torres
Tema 2: Encapsulamiento
2.1. Explicación
El Encapsulamiento es uno de los cuatro pilares de la POO y se refiere a la idea de agrupar
los datos (atributos) y los métodos que operan sobre esos datos dentro de una única
unidad, que es la clase. Su objetivo principal es:
1. Ocultar los detalles internos de implementación: Un usuario de la clase no
necesita saber cómo funcionan internamente los métodos o cómo se almacenan los
datos. Solo necesita saber qué hace la clase y cómo interactuar con ella a través de
su interfaz pública (métodos).
2. Proteger los datos: Evitar que los datos de un objeto sean modificados directamente
desde fuera de la clase de una manera no controlada. Esto permite mantener la
integridad del estado del objeto.
En Python, el encapsulamiento no se implementa con modificadores de acceso estrictos
como public, private o protected (como en Java o C++). En cambio, se utiliza una convención
de nomenclatura y, para un nivel más alto de "privacidad", la mutilación de nombres (name
mangling).
• Miembros Públicos: Son accesibles desde cualquier lugar. En Python, todos los
atributos y métodos son públicos por defecto.
Python
• Miembros "Protegidos" (Convención): Se recomienda no acceder a ellos
directamente desde fuera de la clase o de sus subclases, pero no hay restricción
técnica. Se utiliza un solo guion bajo (_) como prefijo.
Python
Aunque puedes acceder a _atributo_protegido directamente, la convención indica que no
deberías hacerlo. Es una señal para otros desarrolladores.
• Miembros "Privados" (Name Mangling): Python realiza una "mutilación de nombres"
para atributos y métodos que comienzan con doble guion bajo (__). Esto hace que
Programación Orientada a Objetos en Python | Omar Acosta Torres
sean más difíciles de acceder desde fuera de la clase y se considera una forma más
fuerte de encapsulamiento.
Python
Cuando intentas acceder a __atributo_privado desde fuera, Python lo renombra internamente
a _NombreClase__atributo_privado. Esto no lo hace verdaderamente "privado" como en otros
lenguajes, pero hace que sea mucho más complicado el acceso directo.
¿Por qué usar encapsulamiento?
• Control de Acceso: Puedes implementar lógica de validación cuando se establecen
o se obtienen valores de atributos.
• Mantenibilidad: Si cambias la implementación interna de una clase, los usuarios de
esa clase no se verán afectados si solo dependían de la interfaz pública.
• Flexibilidad: Permite que la clase evolucione sin romper el código que la utiliza.
2.2. Ejemplos
Ejemplo 1: Uso de atributos "protegidos" y "privados"
Python
Programación Orientada a Objetos en Python | Omar Acosta Torres
Explicación del ejemplo:
• [Link]: Atributo público, accesible y modificable directamente.
• self._numero_cuenta: Atributo "protegido" por convención. Puedes acceder a él, pero
el guion bajo indica que es un detalle interno.
• self.__saldo: Atributo "privado" por name mangling. Intentar acceder directamente
como mi_cuenta.__saldo fallará. La única forma "recomendada" de interactuar con él
es a través de los métodos depositar, retirar y obtener_saldo. Esto es
encapsulamiento en acción: el control sobre el saldo se realiza a través de métodos,
no de acceso directo.
Ejemplo 2: Usando @property para un encapsulamiento más elegante (Getters y Setters)
Programación Orientada a Objetos en Python | Omar Acosta Torres
El decorador @property es una forma "pythónica" de implementar el encapsulamiento,
permitiendo acceder a métodos como si fueran atributos, lo que resulta en un código más
legible y elegante para "getters" y "setters".
Python
Explicación del ejemplo:
• self._nombre, self._edad: Los atributos reales se definen como "protegidos" con un
solo guion bajo.
Programación Orientada a Objetos en Python | Omar Acosta Torres
• @property def nombre(self):: Este decorador convierte el método nombre en un
"getter". Cuando accedes a [Link], este método se ejecuta.
• @[Link] def nombre(self, nuevo_nombre):: Este decorador define el "setter"
para el atributo nombre. Cuando intentas asignar un valor a [Link] = "Anabella",
este método se ejecuta, permitiendo la validación.
• Esto logra encapsulamiento porque el acceso al _nombre y _edad está controlado por
los métodos nombre y edad, que pueden incluir lógica de validación, registro, etc., sin
que el usuario de la clase tenga que llamar a set_nombre() o get_nombre().
2.3. Taller Propuesto
Taller 2: Gestión de Empleados con Encapsulamiento
Objetivo: Aplicar los conceptos de encapsulamiento utilizando atributos "privados" (name
mangling) y el decorador @property para controlar el acceso a la información sensible de un
empleado.
Instrucciones:
1. Define una clase llamada Empleado que represente a un trabajador en una
empresa.
2. La clase Empleado debe tener los siguientes atributos "privados" (usando doble
guion bajo __):
o __nombre (cadena de texto)
o __salario (número flotante)
o __departamento (cadena de texto)
3. Define un método constructor (__init__) para la clase Empleado que inicialice estos
tres atributos. Asegúrate de que el salario inicial sea mayor que cero.
4. Implementa los siguientes métodos para acceder y modificar los atributos
(Getters y Setters), utilizando el decorador @property para nombre, salario y
departamento:
o nombre (Getter): Devuelve el nombre del empleado.
o nombre (Setter): Permite cambiar el nombre del empleado. La nueva cadena
no debe estar vacía.
o salario (Getter): Devuelve el salario del empleado.
o salario (Setter): Permite cambiar el salario del empleado. El nuevo salario
debe ser un número positivo.
o departamento (Getter): Devuelve el departamento del empleado.
Programación Orientada a Objetos en Python | Omar Acosta Torres
o departamento (Setter): Permite cambiar el departamento del empleado. La
nueva cadena no debe estar vacía.
5. Define un método adicional mostrar_informacion(self) que imprima todos los
detalles del empleado (nombre, salario, departamento).
6. Crea al menos dos objetos (instancias) de la clase Empleado con información
diferente.
7. Realiza las siguientes operaciones con tus objetos:
o Muestra la información de ambos empleados.
o Intenta cambiar el nombre de un empleado a una cadena vacía. Verifica si la
validación funciona.
o Intenta cambiar el salario de un empleado a un valor negativo o cero. Verifica
si la validación funciona.
o Cambia el departamento de un empleado a un departamento válido y luego
muestra su información.
o Intenta acceder directamente a los atributos __nombre o __salario (esto
debería generar un AttributeError).
Programación Orientada a Objetos en Python | Omar Acosta Torres
Tema 3: Herencia
3.1. Explicación
La Herencia es un pilar fundamental de la POO que permite crear una nueva clase a partir de
una clase existente. La nueva clase (llamada clase hija, subclase o clase derivada) "hereda"
los atributos y métodos de la clase existente (llamada clase padre, superclase o clase
base).
Conceptos Clave:
• Reutilización de Código: Evita la duplicación de código. Si varias clases comparten
características y comportamientos comunes, se pueden definir en una clase padre y
luego heredar por las clases hijas.
• Relación "es un": La herencia modela una relación "es un". Por ejemplo, un Perro "es
un" Animal, un Coche "es un" Vehiculo.
• Extensión de Funcionalidad: Las clases hijas pueden añadir nuevos atributos y
métodos, o pueden sobrescribir (modificar el comportamiento de) los métodos
heredados de la clase padre para adaptarlos a sus necesidades específicas.
Sintaxis de la Herencia en Python:
Python
Método super():
Cuando una clase hija tiene su propio constructor (__init__) o necesita llamar a un método
que ha sido sobrescrito en la clase padre, se utiliza la función super(). Esto es especialmente
útil en el constructor de la clase hija para asegurar que el constructor de la clase padre
también inicialice los atributos que le corresponden.
Python
Programación Orientada a Objetos en Python | Omar Acosta Torres
3.2. Ejemplos
Ejemplo 1: Herencia Simple
Python
Explicación del ejemplo:
• La clase Animal define nombre, edad, comer() y dormir().
• Perro(Animal) y Gato(Animal) indican que Perro y Gato heredan de Animal.
Programación Orientada a Objetos en Python | Omar Acosta Torres
• En los constructores de Perro y Gato, super().__init__(nombre, edad) se encarga de
inicializar la parte de Animal del objeto.
• Cada subclase añade sus propios atributos (raza, color_pelo) y métodos (ladrar(),
maullar()).
• Los objetos mi_perro y mi_gato pueden usar tanto los métodos heredados (comer(),
dormir()) como los suyos propios.
Ejemplo 2: Sobreescritura de Métodos
Las clases hijas pueden sobrescribir métodos de la clase padre para proporcionar una
implementación diferente.
Python
Explicación del ejemplo:
Programación Orientada a Objetos en Python | Omar Acosta Torres
• Coche sobrescribe el método conducir() para dar un mensaje más específico.
• Motocicleta sobrescribe el método describir().
• Cuando llamas a mi_coche.conducir(), se ejecuta la versión del método definida en
Coche.
• Cuando llamas a mi_moto.conducir(), se ejecuta la versión heredada de Vehiculo
porque no fue sobrescrita en Motocicleta.
3.3. Taller Propuesto
Taller 3: Sistema de Empleados Jerárquico
Objetivo: Diseñar un sistema de clases utilizando herencia para modelar diferentes tipos de
empleados en una empresa.
Instrucciones:
1. Define una clase base Empleado con los siguientes atributos y métodos:
o Atributos de instancia: nombre, id_empleado, salario_base.
o Método constructor __init__: Inicializa los atributos.
o Método calcular_salario_bruto(self): Retorna el salario_base. (Este será
sobrescrito en las subclases).
o Método mostrar_informacion(self): Imprime el nombre, ID y salario bruto.
2. Define una subclase Programador que herede de Empleado:
o Atributos de instancia adicionales: lenguaje_principal (ej. "Python", "Java").
o Método constructor __init__: Llama al constructor de la clase padre y luego
inicializa lenguaje_principal.
o Sobrescribe el método calcular_salario_bruto(self): Los programadores
tienen un bono del 10% sobre su salario_base. Retorna el salario_base más el
bono.
o Método adicional codificar(self): Imprime un mensaje como "[Nombre del
Programador] está codificando en [Lenguaje Principal]."
3. Define una subclase Gerente que herede de Empleado:
o Atributos de instancia adicionales: num_empleados_a_cargo (entero).
o Método constructor __init__: Llama al constructor de la clase padre y luego
inicializa num_empleados_a_cargo.
o Sobrescribe el método calcular_salario_bruto(self): Los gerentes tienen un
bono de $50 por cada empleado a su cargo. Retorna el salario_base más este
bono.
Programación Orientada a Objetos en Python | Omar Acosta Torres
o Método adicional organizar_reunion(self): Imprime un mensaje como
"[Nombre del Gerente] está organizando una reunión con [Número de
Empleados] empleados."
4. Crea instancias de cada tipo de empleado:
o Un Empleado general.
o Dos Programadores con diferentes lenguajes y salarios.
o Un Gerente con un número de empleados a cargo.
5. Realiza las siguientes operaciones:
o Para cada empleado, llama a mostrar_informacion() para ver su salario bruto.
o Para los Programadores, llama a codificar().
o Para el Gerente, llama a organizar_reunion().
o Demuestra que un Programador o Gerente todavía puede acceder a los
atributos heredados como id_empleado.
Programación Orientada a Objetos en Python | Omar Acosta Torres
Tema 4: Polimorfismo
4.1. Explicación
El Polimorfismo (del griego "poli" = muchas, "morfos" = formas) es el cuarto pilar de la POO y
se refiere a la capacidad de un objeto de tomar muchas formas o, más precisamente, la
capacidad de una misma interfaz de ser utilizada para diferentes tipos de datos o acciones.
En el contexto de la POO, significa que:
• Los objetos de diferentes clases pueden responder al mismo método de
diferentes maneras. Aunque el nombre del método es el mismo, la implementación
específica varía según la clase del objeto que lo invoca.
• Esto permite escribir código más genérico y flexible, ya que puedes interactuar con
objetos de diferentes tipos de manera uniforme, siempre y cuando compartan una
interfaz común (es decir, implementen los mismos métodos).
Tipos de Polimorfismo en Python:
1. Polimorfismo de Inclusión (Herencia y Sobreescritura): Es el tipo más común y se
logra a través de la herencia y la sobrescritura de métodos. Si una clase A hereda de
una clase B, entonces un objeto de tipo A puede ser tratado como un objeto de tipo B.
Si A sobrescribe un método de B, la versión de A se ejecutará cuando el método se
llame en un objeto A, incluso si se le trata como un B.
2. Polimorfismo por Coerción (Sobrecarga de Operadores): Python permite redefinir
el comportamiento de los operadores estándar (como +, -, *, ==) para objetos de
clases personalizadas. Esto se hace implementando "métodos mágicos" o "dunder
methods" (ej., __add__, __mul__, __eq__).
3. Polimorfismo Paramétrico (Sobrecarga de Métodos - con matices en Python): A
diferencia de otros lenguajes (como Java o C++), Python no soporta la sobrecarga de
métodos de la misma manera (definiendo múltiples métodos con el mismo nombre
pero diferente número o tipo de argumentos). En Python, la última definición del
método con el mismo nombre sobrescribe las anteriores. El polimorfismo se logra
típicamente con valores por defecto para los parámetros, argumentos variables
(*args, **kwargs), o comprobaciones de tipo dentro del método.
Beneficios del Polimorfismo:
• Mayor Flexibilidad: Puedes escribir código que funcione con cualquier objeto que
implemente una determinada interfaz, sin necesidad de saber el tipo exacto del
objeto.
• Código Más Limpio: Reduce la necesidad de sentencias if/else anidadas o switch
para manejar diferentes tipos de objetos.
• Facilita la Extensibilidad: Es fácil añadir nuevas clases que implementen la misma
interfaz sin tener que modificar el código existente que las utiliza.
Programación Orientada a Objetos en Python | Omar Acosta Torres
4.2. Ejemplos
Ejemplo 1: Polimorfismo de Inclusión (Herencia y Sobreescritura)
Python
Explicación del ejemplo:
• La clase Animal define un método hacer_sonido, que es un contrato o interfaz.
• Perro, Gato y Vaca heredan de Animal y cada uno sobrescribe hacer_sonido para dar
una implementación específica.
• La función escuchar_animal(animal) no sabe si el animal es un Perro, un Gato o una
Vaca. Simplemente sabe que el objeto animal tiene un método hacer_sonido(). Esto
es polimorfismo: la misma llamada (animal.hacer_sonido()) produce un
comportamiento diferente dependiendo del tipo de objeto.
Ejemplo 2: Polimorfismo por Coerción (Sobrecarga de Operadores)
Python
Programación Orientada a Objetos en Python | Omar Acosta Torres
Explicación del ejemplo:
• Los métodos __add__, __mul__ y __eq__ son métodos especiales de Python que
permiten definir cómo los objetos de la clase Vector2D se comportan cuando se usan
los operadores +, * y == respectivamente.
Programación Orientada a Objetos en Python | Omar Acosta Torres
• Esto es polimorfismo porque el operador + puede sumar números, concatenar
cadenas, y ahora, sumar objetos Vector2D, todo con la misma interfaz de operador.
4.3. Taller Propuesto
Taller 4: Simulador de Interacción de Dispositivos
Objetivo: Implementar polimorfismo de inclusión para simular la interacción con diferentes
tipos de dispositivos electrónicos.
Instrucciones:
1. Define una clase base abstracta (o con un método base que no haga nada)
llamada DispositivoElectronico.
o Debe tener un método encender(self) que imprima "El dispositivo se está
encendiendo."
o Debe tener un método apagar(self) que imprima "El dispositivo se está
apagando."
o Debe tener un método ejecutar_tarea(self) que simplemente imprima
"Ejecutando tarea genérica..." (Este será el método polimórfico).
2. Define las siguientes subclases que hereden de DispositivoElectronico:
o Telefono:
▪ Atributos de instancia adicionales: numero_telefono.
▪ Constructor __init__: Inicializa numero_telefono y llama al
constructor del padre.
▪ Sobrescribe ejecutar_tarea(self): Imprime "Realizando una llamada
desde [número de teléfono]."
▪ Método adicional enviar_mensaje(self, mensaje): Imprime
"Enviando mensaje: [mensaje]".
o Ordenador:
▪ Atributos de instancia adicionales: sistema_operativo.
▪ Constructor __init__: Inicializa sistema_operativo y llama al
constructor del padre.
▪ Sobrescribe ejecutar_tarea(self): Imprime "Abriendo un programa en
el sistema operativo [sistema operativo]."
▪ Método adicional instalar_software(self, software): Imprime
"Instalando [software] en el ordenador."
o SmartTV:
Programación Orientada a Objetos en Python | Omar Acosta Torres
▪ Atributos de instancia adicionales: pulgadas.
▪ Constructor __init__: Inicializa pulgadas y llama al constructor del
padre.
▪ Sobrescribe ejecutar_tarea(self): Imprime "Reproduciendo
contenido en la Smart TV de [pulgadas] pulgadas."
▪ Método adicional cambiar_canal(self, canal): Imprime "Cambiando
al canal [canal] en la Smart TV."
3. Crea una función llamada interactuar_con_dispositivo(dispositivo):
o Esta función debe tomar un objeto dispositivo como parámetro.
o Debe llamar a [Link]().
o Debe llamar a dispositivo.ejecutar_tarea().
o Debe llamar a [Link]().
4. Crea instancias de cada tipo de dispositivo:
o Un Telefono.
o Un Ordenador.
o Una SmartTV.
5. Demuestra el polimorfismo:
o Utiliza la función interactuar_con_dispositivo() con cada una de las instancias
creadas. Observa cómo el método ejecutar_tarea() se comporta de manera
diferente según el tipo de dispositivo, a pesar de que la llamada a la función es
la misma.
o Crea una lista que contenga objetos de Telefono, Ordenador y SmartTV. Itera
sobre esta lista y llama a interactuar_con_dispositivo() para cada elemento.
Programación Orientada a Objetos en Python | Omar Acosta Torres
Tema 5: Abstracción
5.1. Explicación
La Abstracción es uno de los pilares más importantes de la POO. Se trata de mostrar solo
los detalles esenciales de un objeto y ocultar la complejidad interna de su
implementación. Imagina que conduces un coche: no necesitas saber exactamente cómo
funciona el motor o la transmisión para poder usarlo; solo te importa la interfaz (volante,
pedales, palanca de cambios) que te permite interactuar con él.
En POO, la abstracción se consigue:
1. Mediante el diseño de clases y métodos: Al definir una clase, estamos creando una
abstracción de una entidad del mundo real. Los métodos de la clase exponen la
funcionalidad (qué se puede hacer), pero ocultan cómo se hace.
2. Utilizando clases y métodos abstractos: Python, a través del módulo abc (Abstract
Base Classes), permite definir clases abstractas y métodos abstractos.
o Una clase abstracta es una clase que no puede ser instanciada directamente
(no se pueden crear objetos de ella). Su propósito es ser una clase base para
otras clases.
o Un método abstracto es un método declarado en una clase abstracta que no
tiene implementación en esa clase. Las subclases que heredan de una clase
abstracta deben implementar todos sus métodos abstractos; de lo contrario,
la subclase también se considerará abstracta y no podrá ser instanciada.
La abstracción es crucial para:
• Simplificar la complejidad: Los usuarios de la clase no necesitan preocuparse por
los detalles internos, solo por cómo usar la interfaz pública.
• Mejorar la mantenibilidad: Los cambios en la implementación interna de una clase
no afectan a las otras partes del código que la utilizan, siempre que la interfaz pública
se mantenga.
• Fomentar el diseño de interfaces: Fuerza a los desarrolladores a pensar en la
funcionalidad que una clase debe proporcionar, en lugar de cómo la va a
implementar.
5.2. Ejemplos
Ejemplo 1: Abstracción sin abc (mediante convención)
Antes de Python 3.x y el módulo abc, la abstracción se lograba mediante convenciones,
indicando que un método estaba "vacío" o lanzaba un error si no se sobrescribía.
Python
Programación Orientada a Objetos en Python | Omar Acosta Torres
Explicación del ejemplo:
• FormaGeometrica tiene un método calcular_area que lanza un NotImplementedError.
Esto es una forma de "forzar" a las subclases a implementarlo, actuando como un
método abstracto por convención.
Ejemplo 2: Abstracción con el módulo abc (Abstract Base Classes)
Esta es la forma recomendada y explícita de implementar la abstracción en Python.
Python
Programación Orientada a Objetos en Python | Omar Acosta Torres
Programación Orientada a Objetos en Python | Omar Acosta Torres
Explicación del ejemplo:
• from abc import ABC, abstractmethod: Importa lo necesario para definir clases
abstractas.
• class Dispositivo(ABC):: Indica que Dispositivo es una Clase Base Abstracta.
• @abstractmethod def encender(self): pass: Define encender como un método
abstracto. No tiene implementación.
• Si una subclase (como Telefono o Computadora) no implementa todos los métodos
abstractos de su clase base, Python no permitirá crear instancias de esa subclase,
lanzando un TypeError. Esto asegura que las clases concretas cumplan con el
contrato definido por la clase abstracta.
5.3. Taller Propuesto
Taller 5: Procesador de Pagos Abstracto
Objetivo: Crear una clase base abstracta para procesadores de pagos y luego implementar
procesadores concretos para diferentes métodos de pago, demostrando la abstracción.
Instrucciones:
1. Importa ABC y abstractmethod del módulo abc.
2. Define una clase abstracta llamada ProcesadorPago(ABC) con los siguientes
métodos abstractos:
o procesar_pago(self, monto): Debe recibir un monto (flotante) y simular el
procesamiento del pago.
o reembolsar_pago(self, id_transaccion): Debe recibir un id_transaccion
(cadena de texto) y simular el reembolso.
3. Define una subclase concreta ProcesadorTarjetaCredito que herede de
ProcesadorPago:
o Constructor __init__: Recibe un atributo tarjeta_numero (simulando el
número de tarjeta para este procesador).
o Implementa procesar_pago(self, monto): Imprime un mensaje como
"Procesando $[monto] con tarjeta de crédito [número de tarjeta]. Transacción
ID: [ID único generado]." Genera un ID de transacción simple (ej. f"TC-
{[Link](1000, 9999)}").
o Implementa reembolsar_pago(self, id_transaccion): Imprime un mensaje
como "Reembolsando transacción ID: [ID de transacción] a tarjeta de crédito
[número de tarjeta]."
4. Define una subclase concreta ProcesadorPayPal que herede de ProcesadorPago:
Programación Orientada a Objetos en Python | Omar Acosta Torres
o Constructor __init__: Recibe un atributo correo_paypal (simulando la cuenta
de PayPal).
o Implementa procesar_pago(self, monto): Imprime un mensaje como
"Procesando $[monto] a través de PayPal para cuenta [correo de PayPal].
Transacción ID: [ID único generado]." Genera un ID de transacción simple (ej.
f"PP-{[Link](1000, 9999)}").
o Implementa reembolsar_pago(self, id_transaccion): Imprime un mensaje
como "Reembolsando transacción ID: [ID de transacción] a cuenta PayPal
[correo de PayPal]."
5. Crea una función auxiliar simular_compra(procesador, monto):
o Toma un objeto procesador (de tipo ProcesadorPago o sus subclases) y un
monto.
o Llama a procesador.procesar_pago(monto) y almacena el ID de transacción
retornado (si lo hay, o simplemente imprímelo y asúmelo para el reembolso).
o Luego llama a procesador.reembolsar_pago() con un ID de transacción de
ejemplo.
6. Crea instancias de cada procesador de pago concreto:
o Un ProcesadorTarjetaCredito.
o Un ProcesadorPayPal.
7. Demuestra la abstracción y el polimorfismo:
o Utiliza la función simular_compra() con cada uno de los procesadores
concretos. Observa cómo, a pesar de que la función simular_compra no sabe
el tipo exacto de procesador, puede invocar los métodos procesar_pago y
reembolsar_pago de manera uniforme, y cada procesador responderá de
forma específica.
Programación Orientada a Objetos en Python | Omar Acosta Torres
Tema 6: Dunder Methods (Métodos Mágicos/Especiales)
6.1. Explicación
Los Dunder Methods (también conocidos como "métodos mágicos" o "métodos especiales")
son métodos en Python que tienen un doble guion bajo al principio y al final de su nombre
(ej., __init__, __str__, __add__). Estos métodos no están destinados a ser llamados
directamente por el usuario, sino que son invocados automáticamente por Python en
respuesta a ciertas operaciones o eventos.
Permiten que tus objetos personalizados interactúen con las características incorporadas del
lenguaje, como operadores, funciones integradas, iteración, serialización, etc. Al
implementar estos métodos, puedes hacer que tus clases se comporten de manera similar a
los tipos de datos incorporados de Python (listas, diccionarios, números), lo que resulta en
un código más "pythónico" e intuitivo.
Algunos Dunder Methods comunes incluyen:
• __init__(self, ...): El constructor de la clase, invocado al crear una nueva instancia.
• __str__(self): Define la representación "legible para el usuario" de un objeto, utilizada
por str() y print().
• __repr__(self): Define la representación "oficial" o "inequívoca" de un objeto, utilizada
por repr() y en la consola interactiva. Debería ser una cadena que, idealmente, pueda
reconstruir el objeto.
• __len__(self): Define el comportamiento de la función len().
• __add__(self, other): Define el comportamiento del operador +.
• __eq__(self, other): Define el comportamiento del operador de igualdad ==.
• __lt__(self, other): Define el comportamiento del operador "menor que" <.
• __call__(self, *args, **kwargs): Permite que una instancia de una clase sea llamada
como una función.
• __getitem__(self, key): Define el comportamiento al acceder a un elemento por índice
o clave (ej., obj[key]).
• __setitem__(self, key, value): Define el comportamiento al asignar un valor a un
elemento por índice o clave (ej., obj[key] = value).
• __delitem__(self, key): Define el comportamiento al eliminar un elemento por índice o
clave (ej., del obj[key]).
• __iter__(self) y __next__(self): Permiten que un objeto sea iterable (utilizado en bucles
for).
• __enter__(self) y __exit__(self): Para gestionar recursos (context managers, usados
con with).
Programación Orientada a Objetos en Python | Omar Acosta Torres
6.2. Ejemplos
Ejemplo 1: __str__ y __repr__
Python
Explicación del ejemplo:
• __str__ proporciona una salida amigable para el usuario.
• __repr__ proporciona una salida más detallada y útil para la depuración o para recrear
el objeto. Es una buena práctica que si solo se implementa uno, sea __repr__.
Ejemplo 2: Sobrecarga de Operadores (__add__, __eq__, __len__)
Python
Programación Orientada a Objetos en Python | Omar Acosta Torres
Explicación del ejemplo:
• __len__: Permite que len(libro1) retorne el número de páginas.
• __add__: Define lo que sucede cuando sumas dos objetos Libro o un Libro con un
número. En este caso, suma sus páginas.
• __eq__: Define cuándo dos objetos Libro se consideran iguales (cuando tienen el
mismo título y autor).
6.3. Taller Propuesto
Taller 6: Sistema de Gestión de Tareas con Dunder Methods
Objetivo: Crear una clase Tarea y mejorar su funcionalidad utilizando varios Dunder Methods
para hacerla más intuitiva y "pythónica".
Programación Orientada a Objetos en Python | Omar Acosta Torres
Instrucciones:
1. Define una clase llamada Tarea con los siguientes atributos en su constructor
(__init__):
o descripcion (cadena de texto)
o fecha_limite (objeto [Link], para el cual necesitarás from datetime
import date).
o completada (booleano, False por defecto).
2. Implementa los siguientes Dunder Methods para la clase Tarea:
o __str__(self): Debe retornar una representación legible de la tarea, incluyendo
su descripción, fecha límite y estado (ej. "Tarea: Comprar víveres (Fecha
límite: 2025-12-31) - Pendiente").
o __repr__(self): Debe retornar una representación inequívoca que podría
usarse para recrear el objeto (ej. Tarea('Comprar víveres', date(2025, 12, 31),
False)).
o __eq__(self, other): Dos tareas se consideran iguales si tienen la misma
descripcion y fecha_limite.
o __lt__(self, other): Una tarea es "menor que" otra si su fecha_limite es
anterior. Esto permitirá ordenar tareas por fecha.
o __bool__(self): Debe retornar True si la tarea está completada, y False si está
Pendiente. Esto permitirá usar las tareas en contextos booleanos (ej. if tarea:).
3. Define un método adicional marcar_completada(self) que cambia el estado de
completada a True.
4. Crea varias instancias de la clase Tarea:
o Al menos dos tareas con fechas límite diferentes.
o Una tarea con la misma descripción y fecha límite que otra, pero diferente
estado.
5. Realiza las siguientes operaciones para demostrar los Dunder Methods:
o Imprime tus objetos Tarea directamente para ver el efecto de __str__.
o Usa repr() en uno de tus objetos para ver el efecto de __repr__.
o Compara dos tareas usando == para verificar __eq__.
o Compara dos tareas usando < para verificar __lt__ y luego crea una lista de
tareas y ordénala ([Link]()). Imprime la lista ordenada.
Programación Orientada a Objetos en Python | Omar Acosta Torres
o Usa una tarea en una sentencia if para ver el efecto de __bool__. Marca una
tarea como completada y repite la prueba del if.
Programación Orientada a Objetos en Python | Omar Acosta Torres
Tema 7: Principios SOLID
7.1. Explicación
Los Principios SOLID son un acrónimo de cinco principios de diseño de software orientados
a objetos propuestos por Robert C. Martin (también conocido como "Uncle Bob"). Su objetivo
es hacer que los diseños de software sean más comprensibles, flexibles y mantenibles.
Seguir estos principios puede ayudar a construir sistemas robustos que sean fáciles de
refactorizar y de extender.
Los cinco principios son:
1. S - Single Responsibility Principle (SRP - Principio de Responsabilidad Única):
o Idea: Una clase debe tener una y solo una razón para cambiar, es decir, debe
tener una única responsabilidad.
o Significado: Esto no significa que una clase deba tener solo un método o un
atributo. Significa que todos los elementos de una clase deben estar
orientados a una única tarea o un único aspecto de la funcionalidad del
sistema. Si una clase tiene múltiples responsabilidades, los cambios en una
de ellas pueden afectar inesperadamente a las otras.
2. O - Open/Closed Principle (OCP - Principio de Abierto/Cerrado):
o Idea: Las entidades de software (clases, módulos, funciones, etc.) deben
estar abiertas para la extensión, pero cerradas para la modificación.1
o Significado: Deberías poder añadir nuevas funcionalidades a un sistema sin
tener que modificar el código existente que ya funciona. Esto se logra
típicamente mediante el uso de abstracciones (interfaces o clases abstractas)
y polimorfismo.
3. L - Liskov Substitution Principle (LSP - Principio de Sustitución de Liskov):
o Idea: Los objetos de una superclase deben poder ser reemplazados por
objetos de sus subclases sin alterar la corrección del programa.
o Significado: Si la clase B es una subclase de la clase A, entonces en cualquier
lugar donde se espere un objeto de tipo A, se puede usar un objeto de tipo B
sin que el código falle. Esto implica que las subclases no deben romper las
expectativas o el contrato definido por la superclase (por ejemplo, al cambiar
drásticamente el comportamiento de un método sobrescrito o al no cumplir
con las precondiciones/postcondiciones).
4. I - Interface Segregation Principle (ISP - Principio de Segregación de Interfaces):
o Idea: Los clientes no deben ser forzados a depender de interfaces que no
utilizan.
Programación Orientada a Objetos en Python | Omar Acosta Torres
o Significado: Es mejor tener muchas interfaces pequeñas y específicas para el
cliente (roles) que una gran interfaz monolítica. Si una clase implementa una
interfaz, solo debería tener que implementar los métodos que realmente
necesita. Esto evita que las clases implementen métodos que no les
corresponden y reduce la acoplación.
5. D - Dependency Inversion Principle (DIP - Principio de Inversión de
Dependencias):
o Idea: Los módulos de alto nivel no deben depender de módulos de bajo nivel.
Ambos deben depender de abstracciones. Las abstracciones no deben
depender de2 los detalles. Los detalles deben depender de las abstracciones.3
o Significado: En lugar de que las clases de alto nivel (que orquestan la lógica
de negocio) dependan directamente de implementaciones concretas de
clases de bajo nivel (que realizan tareas específicas), ambas deberían
depender de interfaces o clases abstractas. Esto hace que el sistema sea más
flexible y fácil de probar, ya que las implementaciones concretas pueden ser
intercambiadas fácilmente.
7.2. Ejemplos (Breves, ya que SOLID es más de diseño)
Los ejemplos de SOLID a menudo son más grandes y demuestran cómo estructurar un
sistema completo. Aquí veremos ejemplos conceptuales y mini-ejemplos.
Ejemplo 1: Single Responsibility Principle (SRP)
Mal diseño (violando SRP):
Python
Problema: Esta clase tiene tres responsabilidades: gestión de usuarios en DB, envío de
emails, y validación de emails. Si cambia cómo se envían los emails, o cómo se valida un
email, esta clase tendrá que modificarse.
Mejor diseño (aplicando SRP):
Python
Programación Orientada a Objetos en Python | Omar Acosta Torres
Explicación: Ahora cada clase tiene una única razón para cambiar. Si cambia la lógica de
persistencia, solo se toca RepositorioUsuarios. Si cambia el sistema de envío de emails, solo
se toca ServicioEmail.
Ejemplo 2: Open/Closed Principle (OCP)
Mal diseño (violando OCP):
Python
Problema: Cada vez que se añade una nueva forma, la clase CalculadoraArea debe
modificarse (if/elif). No está cerrada a la modificación.
Mejor diseño (aplicando OCP):
Python
Programación Orientada a Objetos en Python | Omar Acosta Torres
Explicación: La clase CalculadoraArea está cerrada para la modificación porque no
necesita ser cambiada si añades una nueva Forma. Está abierta para la extensión porque
puedes añadir nuevas formas (ej. Triangulo) simplemente creando una nueva clase que
herede de Forma e implemente calcular_area().
Ejemplo 3: Dependency Inversion Principle (DIP)
Mal diseño (violando DIP):
Python
Programación Orientada a Objetos en Python | Omar Acosta Torres
Problema: El Coche depende directamente de MotorDiesel. Si quieres cambiar a un
MotorGasolina, tienes que modificar la clase Coche.
Mejor diseño (aplicando DIP):
Python
Explicación: El Coche ahora depende de la abstracción Motor, no de MotorDiesel o
MotorGasolina. La implementación concreta del motor se "inyecta" en el Coche (mediante el
constructor). Esto invierte la dependencia: en lugar de que el alto nivel dependa del bajo
nivel, ambos dependen de la abstracción.
7.3. Taller Propuesto
Taller 7: Sistema de Notificaciones con Principios SOLID
Objetivo: Diseñar un sistema de notificaciones aplicando los principios SRP, OCP y DIP para
crear una solución flexible y extensible.
Instrucciones:
1. Principio de Responsabilidad Única (SRP):
o Crea una clase Notificacion que solo sea responsable de contener los datos
de una notificación (mensaje, destinatario). No debe saber cómo enviarse.
o Crea una clase EmailSender que solo sea responsable de enviar emails.
o Crea una clase SMSSender que solo sea responsable de enviar SMS.
2. Principio Abierto/Cerrado (OCP) y Principio de Inversión de Dependencias (DIP):
Programación Orientada a Objetos en Python | Omar Acosta Torres
o Define una clase base abstracta Notificador(ABC) (usa from abc import
ABC, abstractmethod) con un método abstracto enviar(self, notificacion).
o Haz que EmailSender y SMSSender (y cualquier futuro tipo de remitente,
como PushNotificationSender) hereden de Notificador e implementen su
método enviar().
o Crea una clase GestorNotificaciones que tenga un método
enviar_notificacion(self, notificador, notificacion). Este método debe tomar un
objeto notificador (que sea una instancia de Notificador) y una notificacion
(instancia de Notificacion).
3. Implementa las Clases:
Python
4. Crea instancias y demuestra el sistema:
o Crea un objeto Notificacion.
o Crea instancias de EmailSender y SMSSender.
o Crea una instancia de GestorNotificaciones.
o Usa GestorNotificaciones para enviar la misma Notificacion tanto por email
como por SMS, demostrando que GestorNotificaciones no necesita cambiar
para soportar nuevos tipos de envío (OCP) y que depende de la abstracción
Notificador (DIP).