La Programación Orientada a Objetos (POO) en Python es uno de los paradigmas más importantes del desarrollo de software. Dominar clases, herencia, polimorfismo y encapsulamiento es imprescindible para trabajar con frameworks como Django, FastAPI o Scrapy, y para superar entrevistas técnicas en empresas de tecnología. En este artículo encontrarás ejercicios de POO en Python resueltos con código completo y explicación paso a paso.

¿Qué es la POO en Python?

La POO organiza el código en clases (plantillas) y objetos (instancias concretas). Los cuatro pilares son:

  • Encapsulamiento: ocultar el estado interno y exponer solo lo necesario
  • Herencia: reutilizar código de una clase padre en clases hijas
  • Polimorfismo: distintos objetos responden al mismo método de formas diferentes
  • Abstracción: definir interfaces sin exponer la implementación

Ejercicio 1 — Primera clase: Persona

class Persona:
    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad

    def presentarse(self):
        return f"Hola, soy {self.nombre} y tengo {self.edad} anos."

    def __repr__(self):
        return f"Persona({self.nombre!r}, {self.edad})"

p = Persona("Ana", 30)
print(p.presentarse())
# Hola, soy Ana y tengo 30 anos.

Ejercicio 2 — Encapsulamiento con propiedades

class CuentaBancaria:
    def __init__(self, titular, saldo_inicial=0):
        self._titular = titular
        self.__saldo = saldo_inicial  # atributo privado

    @property
    def saldo(self):
        return self.__saldo

    def depositar(self, cantidad):
        if cantidad > 0:
            self.__saldo += cantidad
            return True
        return False

    def retirar(self, cantidad):
        if 0 < cantidad <= self.__saldo:
            self.__saldo -= cantidad
            return True
        return False

cuenta = CuentaBancaria("Luis", 1000)
cuenta.depositar(500)
cuenta.retirar(200)
print(cuenta.saldo)  # 1300

Ejercicio 3 — Herencia

class Animal:
    def __init__(self, nombre, sonido):
        self.nombre = nombre
        self.sonido = sonido

    def hablar(self):
        return f"{self.nombre} dice: {self.sonido}"

class Perro(Animal):
    def __init__(self, nombre):
        super().__init__(nombre, "Guau!")

    def buscar(self, objeto):
        return f"{self.nombre} va a buscar {objeto}"

class Gato(Animal):
    def __init__(self, nombre):
        super().__init__(nombre, "Miau!")

perro = Perro("Rex")
gato  = Gato("Mimi")
print(perro.hablar())        # Rex dice: Guau!
print(gato.hablar())         # Mimi dice: Miau!
print(perro.buscar("pelota"))# Rex va a buscar pelota

Ejercicio 4 — Polimorfismo

class Forma:
    def area(self):
        raise NotImplementedError

    def __str__(self):
        return f"{self.__class__.__name__}: area = {self.area():.2f}"

class Circulo(Forma):
    def __init__(self, radio):
        self.radio = radio

    def area(self):
        return 3.14159 * self.radio ** 2

class Rectangulo(Forma):
    def __init__(self, ancho, alto):
        self.ancho = ancho
        self.alto = alto

    def area(self):
        return self.ancho * self.alto

formas = [Circulo(5), Rectangulo(4, 6), Circulo(3)]
for forma in formas:
    print(forma)
# Circulo: area = 78.54
# Rectangulo: area = 24.00
# Circulo: area = 28.27

Ejercicio 5 — Herencia múltiple y MRO

class Volador:
    def mover(self):
        return "Volando"

class Nadador:
    def mover(self):
        return "Nadando"

class Pato(Volador, Nadador):
    def quack(self):
        return "Cuac!"

pato = Pato()
print(pato.mover())   # Volando (MRO: Pato -> Volador -> Nadador)
print(pato.quack())   # Cuac!
print(Pato.__mro__)   # muestra la cadena de resolución

Ejercicio 6 — Métodos de clase y estáticos

class Temperatura:
    _conversiones = 0

    def __init__(self, celsius):
        self.celsius = celsius

    @classmethod
    def desde_fahrenheit(cls, fahrenheit):
        cls._conversiones += 1
        return cls((fahrenheit - 32) * 5 / 9)

    @staticmethod
    def punto_congelacion():
        return "El agua congela a 0 C / 32 F"

    @property
    def fahrenheit(self):
        return self.celsius * 9 / 5 + 32

t = Temperatura.desde_fahrenheit(100)
print(f"{t.celsius:.1f} C")  # 37.8 C
print(Temperatura.punto_congelacion())

Ejercicio 7 — Clase abstracta con ABC

from abc import ABC, abstractmethod

class Vehiculo(ABC):
    def __init__(self, marca, modelo):
        self.marca = marca
        self.modelo = modelo

    @abstractmethod
    def combustible(self):
        pass

    def info(self):
        return f"{self.marca} {self.modelo} ({self.combustible()})"

class Coche(Vehiculo):
    def combustible(self):
        return "Gasolina"

class CocheElectrico(Vehiculo):
    def combustible(self):
        return "Electrico"

coches = [Coche("Toyota", "Corolla"), CocheElectrico("Tesla", "Model 3")]
for c in coches:
    print(c.info())

Preguntas frecuentes sobre POO en Python

¿Qué es una clase en Python y para qué sirve?

Una clase es una plantilla que define los atributos (datos) y métodos (comportamientos) que tendrán los objetos creados a partir de ella. Se define con la palabra clave class y se instancia llamándola como función: mi_objeto = MiClase(). Sirve para organizar el código en unidades lógicas reutilizables y modelar entidades del mundo real en el programa.

¿Qué es la herencia en Python?

La herencia permite crear una clase nueva (hija) que reutiliza el código de otra clase existente (padre). La clase hija hereda todos los atributos y métodos del padre, puede sobrescribirlos y añadir los suyos propios. Se define con class Hija(Padre):. Python admite herencia múltiple: class C(A, B):, resolviendo conflictos mediante el MRO (Method Resolution Order).

¿Cuál es la diferencia entre @classmethod y @staticmethod?

Un @classmethod recibe la clase como primer argumento (cls) y puede acceder al estado de la clase o crear instancias alternativas. Un @staticmethod no recibe ni instancia ni clase, actúa como una función normal dentro del espacio de nombres de la clase. Usa @classmethod para métodos de fábrica y @staticmethod para utilidades relacionadas con la clase pero sin acceso a su estado.

¿Qué es el polimorfismo en Python?

El polimorfismo permite que objetos de diferentes clases respondan al mismo método de formas distintas. En Python es especialmente natural gracias al duck typing: si un objeto tiene el método area(), puede usarse en cualquier lugar que espere ese método, sin importar su clase. Esto hace el código flexible y extensible sin necesidad de comprobaciones de tipo.

Conclusión

La POO en Python es un pilar del desarrollo profesional. Practica estos ejercicios y continúa con los ejercicios de funciones en Python, los ejercicios generales de Python y los patrones de diseño en Python para consolidar tus conocimientos.