# **Capítulo 4: Programación orientada a objetos en Python**
## **Página 1: Introducción a la POO**
### ¿Qué es la Programación Orientada a Objetos?
La POO es un paradigma de programación que organiza el código alrededor de objetos
y clases, en lugar de funciones y procedimientos. Python es un lenguaje
multiparadigma que soporta completamente la POO.
### Conceptos fundamentales:
- **Clase**: Molde o plantilla para crear objetos
- **Objeto**: Instancia específica de una clase
- **Atributos**: Características o propiedades del objeto
- **Métodos**: Comportamientos o acciones del objeto
### Pilares de la POO:
1. **Encapsulación**: Ocultar detalles internos
2. **Herencia**: Crear nuevas clases basadas en existentes
3. **Polimorfismo**: Mismo interfaz, diferentes implementaciones
4. **Abstracción**: Simplificar la complejidad
### Ventajas de la POO:
- Código más organizado y reutilizable
- Facilita el mantenimiento
- Modela mejor el mundo real
- Promueve la modularidad
---
## **Página 2: Clases y objetos**
### Definición de una clase:
```python
class Perro:
"""Clase que representa un perro"""
# Atributo de clase (compartido por todas las instancias)
especie = "Canis lupus familiaris"
def __init__(self, nombre, raza, edad):
"""Método constructor - inicializa el objeto"""
# Atributos de instancia (únicos para cada objeto)
[Link] = nombre
[Link] = raza
[Link] = edad
[Link] = 100
def ladrar(self):
"""Método que hace ladrar al perro"""
return f"{[Link]} dice: ¡Guau!"
def correr(self, distancia):
"""Método que hace correr al perro"""
if [Link] >= distancia * 10:
[Link] -= distancia * 10
return f"{[Link]} corrió {distancia} metros"
else:
return f"{[Link]} está muy cansado para correr"
def descansar(self):
"""Método para recuperar energía"""
[Link] = 100
return f"{[Link]} descansó y recuperó toda su energía"
```
### Creación de objetos:
```python
# Crear instancias (objetos) de la clase Perro
mi_perro = Perro("Max", "Labrador", 3)
tu_perro = Perro("Luna", "Pastor Alemán", 2)
# Acceder a atributos
print(mi_perro.nombre) # "Max"
print(tu_perro.raza) # "Pastor Alemán"
# Llamar métodos
print(mi_perro.ladrar()) # "Max dice: ¡Guau!"
print(tu_perro.correr(5)) # "Luna corrió 5 metros"
```
---
## **Página 3: El método __init__ y self**
### El método constructor __init__:
```python
class CuentaBancaria:
def __init__(self, titular, saldo_inicial=0):
"""
Constructor de la clase CuentaBancaria
Args:
titular (str): Nombre del titular de la cuenta
saldo_inicial (float): Saldo inicial de la cuenta
"""
[Link] = titular
[Link] = saldo_inicial
[Link] = True
self.numero_transacciones = 0
def depositar(self, cantidad):
"""Depositar dinero en la cuenta"""
if cantidad > 0 and [Link]:
[Link] += cantidad
self.numero_transacciones += 1
return f"Depósito exitoso. Saldo actual: ${[Link]}"
return "Depósito fallido"
def retirar(self, cantidad):
"""Retirar dinero de la cuenta"""
if 0 < cantidad <= [Link] and [Link]:
[Link] -= cantidad
self.numero_transacciones += 1
return f"Retiro exitoso. Saldo actual: ${[Link]}"
return "Retiro fallido"
def consultar_saldo(self):
"""Consultar saldo actual"""
return f"Saldo de {[Link]}: ${[Link]}"
```
### Uso de self:
```python
# Crear cuentas bancarias
cuenta1 = CuentaBancaria("Ana García", 1000)
cuenta2 = CuentaBancaria("Carlos López")
# Operaciones
print([Link](500)) # "Depósito exitoso. Saldo actual: $1500"
print([Link](100)) # "Retiro fallido"
print(cuenta1.consultar_saldo()) # "Saldo de Ana García: $1500"
# Acceder a atributos
print(f"Titular: {[Link]}")
print(f"Transacciones: {cuenta1.numero_transacciones}")
```
---
## **Página 4: Encapsulación y modificadores de acceso**
### Encapsulación en Python:
Python no tiene modificadores de acceso estrictos como otros lenguajes, pero usa
convenciones:
- **Público**: `atributo` - Accesible desde cualquier lugar
- **Protegido**: `_atributo` - No debería ser accedido externamente
- **Privado**: `__atributo` - Name mangling, difícil acceso externo
### Ejemplo práctico:
```python
class CuentaSegura:
def __init__(self, titular, saldo_inicial, contraseña):
[Link] = titular # Público
self._activa = True # Protegido
self.__saldo = saldo_inicial # Privado
self.__contraseña = contraseña # Privado
# Getter para saldo
def get_saldo(self, contraseña):
"""Método para obtener el saldo de forma segura"""
if contraseña == self.__contraseña:
return self.__saldo
else:
return "Contraseña incorrecta"
# Setter para saldo
def set_saldo(self, cantidad, contraseña):
"""Método para modificar el saldo de forma segura"""
if contraseña == self.__contraseña and cantidad >= 0:
self.__saldo = cantidad
return "Saldo actualizado correctamente"
return "Error: Contraseña incorrecta o cantidad inválida"
def depositar(self, cantidad, contraseña):
"""Depositar con verificación de seguridad"""
if contraseña == self.__contraseña and cantidad > 0:
self.__saldo += cantidad
return f"Depósito exitoso. Nuevo saldo: ${self.__saldo}"
return "Depósito fallido"
```
### Uso de propiedades (@property):
```python
class Persona:
def __init__(self, nombre, edad):
[Link] = nombre
self._edad = edad # Atributo protegido
@property
def edad(self):
"""Getter para edad"""
return self._edad
@[Link]
def edad(self, valor):
"""Setter para edad con validación"""
if 0 <= valor <= 120:
self._edad = valor
else:
print("Edad no válida")
@property
def es_mayor_de_edad(self):
"""Propiedad computada"""
return self._edad >= 18
# Uso
persona = Persona("María", 25)
print([Link]) # 25
print(persona.es_mayor_de_edad) # True
[Link] = 30 # Usa el setter
[Link] = 150 # "Edad no válida"
```
---
## **Página 5: Herencia**
### Herencia básica:
```python
class Animal:
"""Clase base para todos los animales"""
def __init__(self, nombre, edad):
[Link] = nombre
[Link] = edad
def hacer_sonido(self):
return "El animal hace un sonido"
def dormir(self):
return f"{[Link]} está durmiendo"
def comer(self):
return f"{[Link]} está comiendo"
class Perro(Animal):
"""Clase Perro que hereda de Animal"""
def __init__(self, nombre, edad, raza):
# Llamar al constructor de la clase padre
super().__init__(nombre, edad)
[Link] = raza
# Sobrescribir método
def hacer_sonido(self):
return f"{[Link]} dice: ¡Guau!"
# Método específico de Perro
def mover_cola(self):
return f"{[Link]} está moviendo la cola"
class Gato(Animal):
"""Clase Gato que hereda de Animal"""
def __init__(self, nombre, edad, vidas=7):
super().__init__(nombre, edad)
[Link] = vidas
def hacer_sonido(self):
return f"{[Link]} dice: ¡Miau!"
def ronronear(self):
return f"{[Link]} está ronroneando"
```
### Uso de la herencia:
```python
# Crear objetos
mi_perro = Perro("Max", 3, "Labrador")
mi_gato = Gato("Luna", 2)
# Métodos heredados
print(mi_perro.dormir()) # "Max está durmiendo"
print(mi_gato.comer()) # "Luna está comiendo"
# Métodos sobrescritos
print(mi_perro.hacer_sonido()) # "Max dice: ¡Guau!"
print(mi_gato.hacer_sonido()) # "Luna dice: ¡Miau!"
# Métodos específicos
print(mi_perro.mover_cola()) # "Max está moviendo la cola"
print(mi_gato.ronronear()) # "Luna está ronroneando"
```
---
## **Página 6: Herencia múltiple y métodos especiales**
### Herencia múltiple:
```python
class Nadador:
def nadar(self):
return "Está nadando"
class Volador:
def volar(self):
return "Está volando"
class Corredor:
def correr(self):
return "Está corriendo"
class Pato(Nadador, Volador, Corredor):
"""Pato que puede nadar, volar y correr"""
def __init__(self, nombre):
[Link] = nombre
def hacer_todo(self):
return f"{[Link]}: {[Link]()}, {[Link]()}, {[Link]()}"
# Uso
pato = Pato("Donald")
print(pato.hacer_todo())
# "Donald: Está nadando, Está volando, Está corriendo"
```
### Métodos especiales (dunder methods):
```python
class Libro:
def __init__(self, titulo, autor, paginas):
[Link] = titulo
[Link] = autor
[Link] = paginas
# __str__ para representación legible
def __str__(self):
return f"'{[Link]}' por {[Link]}"
# __repr__ para representación técnica
def __repr__(self):
return f"Libro('{[Link]}', '{[Link]}', {[Link]})"
# __len__ para usar len(objeto)
def __len__(self):
return [Link]
# __add__ para sumar objetos
def __add__(self, otro):
return [Link] + [Link]
# __eq__ para comparación
def __eq__(self, otro):
return [Link] == [Link] and [Link] == [Link]
# Uso de métodos especiales
libro1 = Libro("1984", "George Orwell", 328)
libro2 = Libro("Rebelión en la granja", "George Orwell", 112)
print(str(libro1)) # "'1984' por George Orwell"
print(len(libro1)) # 328
print(libro1 + libro2) # 440
print(libro1 == libro2) # False
```
---
## **Página 7: Polimorfismo**
### Polimorfismo en acción:
```python
class Circulo:
def __init__(self, radio):
[Link] = radio
def area(self):
return 3.1416 * [Link] ** 2
def perimetro(self):
return 2 * 3.1416 * [Link]
class Rectangulo:
def __init__(self, ancho, alto):
[Link] = ancho
[Link] = alto
def area(self):
return [Link] * [Link]
def perimetro(self):
return 2 * ([Link] + [Link])
class Triangulo:
def __init__(self, base, altura, lado1, lado2, lado3):
[Link] = base
[Link] = altura
self.lado1 = lado1
self.lado2 = lado2
self.lado3 = lado3
def area(self):
return ([Link] * [Link]) / 2
def perimetro(self):
return self.lado1 + self.lado2 + self.lado3
# Función polimórfica
def calcular_informacion(figura):
"""Función que trabaja con cualquier figura que tenga area() y perimetro()"""
return {
'area': [Link](),
'perimetro': [Link]()
# Uso polimórfico
figuras = [
Circulo(5),
Rectangulo(4, 6),
Triangulo(3, 4, 3, 4, 5)
for figura in figuras:
info = calcular_informacion(figura)
print(f"{figura.__class__.__name__}: Área={info['area']:.2f},
Perímetro={info['perimetro']:.2f}")
```
---
## **Página 8: Clases abstractas**
### Uso de ABC (Abstract Base Classes):
```python
from abc import ABC, abstractmethod
class Forma(ABC):
"""Clase abstracta que define la interfaz para formas geométricas"""
@abstractmethod
def area(self):
"""Calcula el área de la forma"""
pass
@abstractmethod
def perimetro(self):
"""Calcula el perímetro de la forma"""
pass
# Método concreto (no abstracto)
def descripcion(self):
return "Esta es una forma geométrica"
class Cuadrado(Forma):
"""Implementación concreta de Forma"""
def __init__(self, lado):
[Link] = lado
def area(self):
return [Link] ** 2
def perimetro(self):
return 4 * [Link]
class CirculoConcreto(Forma):
"""Otra implementación concreta"""
def __init__(self, radio):
[Link] = radio
def area(self):
return 3.1416 * [Link] ** 2
def perimetro(self):
return 2 * 3.1416 * [Link]
# Uso
cuadrado = Cuadrado(5)
circulo = CirculoConcreto(3)
print(f"Cuadrado - Área: {[Link]()}, Perímetro: {[Link]()}")
print(f"Círculo - Área: {[Link]():.2f}, Perímetro: {[Link]():.2f}")
```
---
## **Página 9: Métodos de clase y estáticos**
### Métodos de clase (@classmethod):
```python
class Persona:
contador_personas = 0 # Variable de clase
def __init__(self, nombre, edad):
[Link] = nombre
[Link] = edad
Persona.contador_personas += 1
@classmethod
def get_total_personas(cls):
"""Método de clase - trabaja con la clase, no con la instancia"""
return cls.contador_personas
@classmethod
def crear_desde_string(cls, cadena):
"""Método de clase alternativo para crear instancias"""
nombre, edad = [Link]('-')
return cls(nombre, int(edad))
class Estudiante(Persona):
contador_estudiantes = 0
def __init__(self, nombre, edad, carrera):
super().__init__(nombre, edad)
[Link] = carrera
Estudiante.contador_estudiantes += 1
@classmethod
def get_total_estudiantes(cls):
return cls.contador_estudiantes
```
### Métodos estáticos (@staticmethod):
```python
class Calculadora:
@staticmethod
def sumar(a, b):
"""Método estático - no necesita self ni cls"""
return a + b
@staticmethod
def es_par(numero):
"""Verifica si un número es par"""
return numero % 2 == 0
@staticmethod
def calcular_imc(peso, altura):
"""Calcula el Índice de Masa Corporal"""
return peso / (altura ** 2)
```
### Uso:
```python
# Métodos de clase
persona1 = Persona("Ana", 25)
persona2 = Persona.crear_desde_string("Carlos-30")
print(Persona.get_total_personas()) # 2
estudiante = Estudiante("María", 20, "Ingeniería")
print(Estudiante.get_total_estudiantes()) # 1
# Métodos estáticos
print([Link](5, 3)) #8
print(Calculadora.es_par(10)) # True
print(f"IMC: {Calculadora.calcular_imc(70, 1.75):.2f}")
```
---
## **Página 10: Proyecto - Sistema de vehículos**
```python
class Vehiculo:
"""Clase base para todos los vehículos"""
def __init__(self, marca, modelo, año, color):
[Link] = marca
[Link] = modelo
self.año = año
[Link] = color
[Link] = 0
[Link] = False
def encender(self):
if not [Link]:
[Link] = True
return f"{[Link]} {[Link]} encendido"
return "El vehículo ya está encendido"
def apagar(self):
if [Link] and [Link] == 0:
[Link] = False
return f"{[Link]} {[Link]} apagado"
return "No se puede apagar en movimiento"
def acelerar(self, incremento):
if [Link]:
[Link] += incremento
return f"Velocidad: {[Link]} km/h"
return "Primero debe encender el vehículo"
def frenar(self, decremento):
if [Link] > 0:
[Link] = max(0, [Link] - decremento)
return f"Velocidad: {[Link]} km/h"
return "El vehículo ya está detenido"
def __str__(self):
return f"{self.año} {[Link]} {[Link]} - {[Link]}"
class Coche(Vehiculo):
def __init__(self, marca, modelo, año, color, num_puertas, tipo_combustible):
super().__init__(marca, modelo, año, color)
self.num_puertas = num_puertas
self.tipo_combustible = tipo_combustible
self.nivel_combustible = 100
def repostar(self, cantidad):
self.nivel_combustible = min(100, self.nivel_combustible + cantidad)
return f"Nivel de combustible: {self.nivel_combustible}%"
def acelerar(self, incremento):
if self.nivel_combustible > 0:
resultado = super().acelerar(incremento)
self.nivel_combustible -= incremento * 0.1
return resultado
return "Sin combustible"
class Motocicleta(Vehiculo):
def __init__(self, marca, modelo, año, color, cilindrada):
super().__init__(marca, modelo, año, color)
[Link] = cilindrada
self.maniobra_riesgo = False
def hacer_caballito(self):
if [Link] and [Link] > 20:
self.maniobra_riesgo = True
return "¡Haciendo caballito!"
return "No se puede hacer caballito"
def acelerar(self, incremento):
# Las motos aceleran más rápido
resultado = super().acelerar(incremento * 1.5)
return [Link]("km/h", "km/h (moto)")
class Camion(Vehiculo):
def __init__(self, marca, modelo, año, color, capacidad_carga):
super().__init__(marca, modelo, año, color)
self.capacidad_carga = capacidad_carga
self.carga_actual = 0
def cargar(self, peso):
if self.carga_actual + peso <= self.capacidad_carga:
self.carga_actual += peso
return f"Carga actual: {self.carga_actual}/{self.capacidad_carga} kg"
return "Excede la capacidad máxima"
def acelerar(self, incremento):
# Los camiones aceleran más lento con carga
factor_carga = max(0.5, 1 - (self.carga_actual / self.capacidad_carga) * 0.5)
resultado = super().acelerar(incremento * factor_carga)
return [Link]("km/h", "km/h (camión)")
# Demo del sistema
if __name__ == "__main__":
# Crear vehículos
mi_coche = Coche("Toyota", "Corolla", 2022, "Rojo", 4, "Gasolina")
mi_moto = Motocicleta("Yamaha", "MT-07", 2023, "Azul", 689)
mi_camion = Camion("Volvo", "FH", 2020, "Blanco", 20000)
# Probar funcionalidades
vehiculos = [mi_coche, mi_moto, mi_camion]
for vehiculo in vehiculos:
print(f"\n=== Probando {vehiculo.__class__.__name__} ===")
print([Link]())
print([Link](30))
# Comportamientos específicos
if isinstance(vehiculo, Coche):
print([Link](20))
elif isinstance(vehiculo, Motocicleta):
print(vehiculo.hacer_caballito())
elif isinstance(vehiculo, Camion):
print([Link](5000))
print([Link](30))
print([Link]())
```
---
## **Página 11: Ejercicios prácticos**
### Ejercicio 1: Sistema de empleados
```python
class Empleado:
def __init__(self, nombre, salario_base, departamento):
[Link] = nombre
self.salario_base = salario_base
[Link] = departamento
self._activo = True
def calcular_salario(self):
return self.salario_base
def despedir(self):
self._activo = False
return f"{[Link]} ha sido despedido/a"
def __str__(self):
estado = "Activo" if self._activo else "Inactivo"
return f"{[Link]} - {[Link]} - {estado}"
class Gerente(Empleado):
def __init__(self, nombre, salario_base, departamento, bono):
super().__init__(nombre, salario_base, departamento)
[Link] = bono
self.empleados_a_cargo = []
def agregar_empleado(self, empleado):
self.empleados_a_cargo.append(empleado)
def calcular_salario(self):
return self.salario_base + [Link] + (len(self.empleados_a_cargo) * 100)
def listar_empleados(self):
return [[Link] for emp in self.empleados_a_cargo]
class Desarrollador(Empleado):
def __init__(self, nombre, salario_base, departamento, lenguaje):
super().__init__(nombre, salario_base, departamento)
[Link] = lenguaje
self.proyectos_completados = 0
def completar_proyecto(self):
self.proyectos_completados += 1
def calcular_salario(self):
return self.salario_base + (self.proyectos_completados * 50)
# Uso del sistema
gerente = Gerente("Ana García", 50000, "TI", 10000)
dev1 = Desarrollador("Carlos López", 40000, "TI", "Python")
dev2 = Desarrollador("María Rodríguez", 42000, "TI", "JavaScript")
gerente.agregar_empleado(dev1)
gerente.agregar_empleado(dev2)
dev1.completar_proyecto()
dev1.completar_proyecto()
dev2.completar_proyecto()
empleados = [gerente, dev1, dev2]
for emp in empleados:
print(f"{emp} - Salario: ${emp.calcular_salario():,}")
print(f"Empleados a cargo de {[Link]}: {gerente.listar_empleados()}")
```
---
## **Página 12: Resumen del capítulo**
### Conceptos aprendidos:
- Clases y objetos (instanciación)
- Método __init__ y self
- Encapsulación y modificadores de acceso
- Herencia simple y múltiple
- Polimorfismo y métodos abstractos
- Métodos especiales (__str__, __len__, etc.)
- Métodos de clase (@classmethod) y estáticos (@staticmethod)
- Propiedades (@property)
### Pilares de la POO dominados:
1. **Encapsulación**: Ocultar detalles con _ y __
2. **Herencia**: Crear jerarquías de clases
3. **Polimorfismo**: Mismo interfaz, diferentes comportamientos
4. **Abstracción**: ABC y métodos abstractos