Curso de Python · Bloque 07 de 09

Clases

Objetos, propiedades, herencia, composición y dataclasses.

75–105 min Descargar notebook ↓

Audiencia. Personas que ya han visto algo de programación y quieren repasar Python de forma práctica.
Prerrequisitos. Funciones, colecciones, excepciones y anotaciones de tipo básicas.
Duración orientativa. 60–90 minutos, incluida la práctica.

Objetivos de aprendizaje

  • Modelar estado y comportamiento con clases e instancias.
  • Usar propiedades, métodos de clase y métodos estáticos.
  • Aplicar herencia, polimorfismo y composición con criterio.
  • Reducir código repetitivo con dataclass y métodos especiales.

Itinerario

  1. Clases e instancias
  2. Atributos y métodos
  3. Propiedades
  4. Métodos alternativos
  5. Herencia
  6. Composición
  7. Dataclasses
  8. Práctica

Cómo trabajar: ejecuta las celdas en orden, predice el resultado antes de verlo y modifica los ejemplos. Cada bloque termina con un ejercicio, un punto de partida y una solución posible.

1. Clases e instancias

Una clase define un nuevo tipo; una instancia es un objeto concreto. __init__ inicializa el estado. self es la instancia sobre la que actúa el método y se pasa automáticamente al llamar.

Python
class Cuenta:
    def __init__(self, titular, saldo=0.0):
        self.titular = titular
        self.saldo = float(saldo)

    def ingresar(self, importe):
        if importe <= 0:
            raise ValueError("el importe debe ser positivo")
        self.saldo += importe
        return self.saldo

cuenta = Cuenta("Alicia", 100)
cuenta.ingresar(35.5)
print(cuenta.titular, cuenta.saldo)
Resultado
Alicia 135.5

2. Atributos de instancia y de clase

Los atributos de instancia pertenecen a cada objeto. Los de clase se comparten y son adecuados para constantes o contadores globales del tipo, no para estado mutable individual.

Python
class Producto:
    impuesto = 0.21
    creados = 0

    def __init__(self, nombre, precio):
        self.nombre = nombre
        self.precio = float(precio)
        type(self).creados += 1

    def precio_final(self):
        return self.precio * (1 + self.impuesto)

teclado = Producto("Teclado", 50)
raton = Producto("Ratón", 20)
print(teclado.precio_final(), Producto.creados)
Resultado
60.5 2

3. Encapsulación y propiedades

Python se apoya en convenciones: _nombre indica uso interno. Una propiedad expone una interfaz de atributo mientras valida o calcula. No hace falta crear getters y setters para todo desde el principio.

Python
class Temperatura:
    def __init__(self, celsius):
        self.celsius = celsius

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, valor):
        valor = float(valor)
        if valor < -273.15:
            raise ValueError("temperatura físicamente imposible")
        self._celsius = valor

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

temperatura = Temperatura(20)
print(temperatura.celsius, temperatura.fahrenheit)
Resultado
20.0 68.0

4. Métodos de clase, estáticos y constructores alternativos

Un classmethod recibe la clase (cls) y suele construir instancias alternativas. Un staticmethod pertenece conceptualmente a la clase, pero no necesita ni instancia ni clase.

Python
class Color:
    def __init__(self, rojo, verde, azul):
        if not all(self.componente_valido(v) for v in (rojo, verde, azul)):
            raise ValueError("cada componente debe estar entre 0 y 255")
        self.rgb = (rojo, verde, azul)

    @staticmethod
    def componente_valido(valor):
        return isinstance(valor, int) and 0 <= valor <= 255

    @classmethod
    def desde_hex(cls, codigo):
        codigo = codigo.removeprefix("#")
        if len(codigo) != 6:
            raise ValueError("se esperan seis dígitos hexadecimales")
        return cls(*(int(codigo[i:i + 2], 16) for i in (0, 2, 4)))

azul = Color.desde_hex("#3366FF")
print(azul.rgb)
Resultado
(51, 102, 255)

5. Métodos especiales

Los métodos __repr__, __str__, __len__, __eq__ y otros integran objetos con la sintaxis de Python. Implementa solo los que tengan semántica natural.

Python
class Cesta:
    def __init__(self, productos=None):
        self.productos = list(productos or [])

    def __len__(self):
        return len(self.productos)

    def __repr__(self):
        return f"Cesta(productos={self.productos!r})"

    def __contains__(self, producto):
        return producto in self.productos

cesta = Cesta(["pan", "leche"])
print(cesta, len(cesta), "pan" in cesta)
Resultado
Cesta(productos=['pan', 'leche']) 2 True

6. Herencia y polimorfismo

La herencia expresa una relación «es un». super() delega a la clase base. El polimorfismo permite tratar objetos diferentes mediante la misma operación. Prefiere jerarquías pequeñas.

Python
from math import pi

class Figura:
    def area(self):
        raise NotImplementedError

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

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

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

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

figuras = [Rectangulo(3, 4), Circulo(2)]
print([round(figura.area(), 2) for figura in figuras])
Resultado
[12, 12.57]

7. Composición

La composición expresa «tiene un» y suele acoplar menos que la herencia. Un objeto delega una parte del trabajo a otro.

Python
class Motor:
    def arrancar(self):
        return "motor en marcha"

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

    def arrancar(self):
        return f"{self.marca}: {self.motor.arrancar()}"

coche = Vehiculo("Ejemplo", Motor())
print(coche.arrancar())
Resultado
Ejemplo: motor en marcha

8. dataclass

Para clases centradas en datos, @dataclass genera inicializador, representación e igualdad. field(default_factory=...) crea una colección nueva por instancia.

Python
from dataclasses import dataclass, field

@dataclass(frozen=True, slots=True)
class Punto:
    x: float
    y: float

    def distancia_al_origen(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5

@dataclass
class Equipo:
    nombre: str
    miembros: list[str] = field(default_factory=list)

punto = Punto(3, 4)
equipo = Equipo("Datos", ["Ada", "Grace"])
print(punto, punto.distancia_al_origen(), equipo)
Resultado
Punto(x=3, y=4) 5.0 Equipo(nombre='Datos', miembros=['Ada', 'Grace'])

Errores habituales

  • Crear una clase cuando basta una función o un diccionario simple.
  • Compartir una lista mutable como atributo de clase por accidente.
  • Hacer jerarquías profundas y frágiles.
  • Exponer estado inválido sin validar invariantes.
  • Añadir propiedades triviales que no aportan una interfaz estable.

Extensión opcional: explora abc.ABC, protocolos y gestores de contexto con __enter__/__exit__.

Práctica · Préstamos de biblioteca

Modela Libro con titulo, autor y prestado. Debe ofrecer:

  • prestar(), que falla si ya está prestado;
  • devolver(), que falla si no lo está;
  • una representación legible;
  • construcción simple mediante @dataclass.
Tu turno
from dataclasses import dataclass

@dataclass
class Libro:
    titulo: str
    autor: str
    prestado: bool = False

    def prestar(self):
        # TODO: valida y cambia el estado.
        pass

    def devolver(self):
        # TODO: valida y cambia el estado.
        pass
Ver una solución posible
Solución
from dataclasses import dataclass

@dataclass
class Libro:
    titulo: str
    autor: str
    prestado: bool = False

    def prestar(self):
        if self.prestado:
            raise RuntimeError("el libro ya está prestado")
        self.prestado = True

    def devolver(self):
        if not self.prestado:
            raise RuntimeError("el libro no estaba prestado")
        self.prestado = False

libro = Libro("El lenguaje de las máquinas", "Ejemplo")
libro.prestar()
print(libro)
libro.devolver()
assert libro.prestado is False
Resultado
Libro(titulo='El lenguaje de las máquinas', autor='Ejemplo', prestado=True)

Comprobación rápida

  • [ ] Sé distinguir estado de instancia y estado de clase.
  • [ ] Puedo elegir entre herencia y composición.
  • [ ] Uso dataclass para modelos de datos sencillos.

← Anterior · Volver al índice · Siguiente →