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
dataclassy métodos especiales.
Itinerario
- Clases e instancias
- Atributos y métodos
- Propiedades
- Métodos alternativos
- Herencia
- Composición
- Dataclasses
- 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.
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)
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.
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)
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.
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)
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.
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)
(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.
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)
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.
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])
[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.
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())
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.
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)
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.
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
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
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
dataclasspara modelos de datos sencillos.