4.D Colecciones de objetos y miembros estáticos¶
Objetos que contienen objetos¶
Los objetos se pueden combinar: un curso tiene alumnos, una biblioteca tiene libros, una persona tiene cuentas. Es la relación de composición («tiene un»), y se resuelve guardando los objetos dentro de otro, normalmente en una lista.
Curso ◆────── 0..n ──────▶ Alumno
(guarda una lista) (nombre, nota)
class Alumno:
creados = 0
def __init__(self, nombre, nota):
if not Alumno.nota_valida(nota):
raise ValueError("nota fuera de rango")
self.nombre = nombre
self.nota = nota
Alumno.creados += 1
@staticmethod
def nota_valida(nota):
return 0 <= nota <= 10
@property
def aprobado(self):
return self.nota >= 5
def __str__(self):
return f"{self.nombre} ({self.nota})"
class Curso:
def __init__(self):
self._alumnos = []
def matricular(self, alumno):
self._alumnos.append(alumno)
@property
def total(self):
return len(self._alumnos)
def media(self):
suma = 0
for a in self._alumnos:
suma += a.nota
return suma / len(self._alumnos)
def mejor(self):
mejor = self._alumnos[0]
for a in self._alumnos:
if a.nota > mejor.nota:
mejor = a
return mejor
def aprobados(self):
return [a for a in self._alumnos if a.aprobado]
if __name__ == "__main__":
curso = Curso()
curso.matricular(Alumno("Ana", 4))
curso.matricular(Alumno("Marta", 8))
curso.matricular(Alumno("Luis", 6))
print(f"Matriculados: {curso.total}")
print(f"Media del curso: {curso.media()}")
print(f"Mejor alumno: {curso.mejor()}")
print("Aprobados: " + ", ".join(a.nombre for a in curso.aprobados()))
try:
Alumno("Error", 11)
except ValueError as e:
print(f"Error: {e}")
print(f"Alumnos creados: {Alumno.creados}")
Salida:
Matriculados: 3
Media del curso: 6.0
Mejor alumno: Marta (8)
Aprobados: Marta, Luis
Error: nota fuera de rango
Alumnos creados: 3
Qué conviene copiar de este diseño:
- El curso decide qué se puede hacer con su lista. Ofrece
matricular,media,mejoryaprobados, pero no entrega la lista para que cualquiera la modifique.for a in self._alumnoso una comprensión de listas. La lista lleva guion bajo (_alumnos): por convención, desde fuera solo se debe matricular o consultar. - Cada clase se ocupa de lo suyo.
Alumnosabe si está aprobado;Cursosabe calcular la media y buscar al mejor.mainsolo orquesta. - Los recorridos son los de siempre (acumular una suma, buscar un máximo, filtrar), solo que ahora recorren objetos y consultan sus atributos y propiedades.
- Las reglas del objeto, en el objeto: la nota se valida en el constructor de
Alumno, así un alumno con nota 11 no llega a existir.
Miembros estáticos¶
Hasta ahora, cada atributo y cada método pertenecía a un objeto concreto. Un miembro estático pertenece a la clase, y es el mismo para todos los objetos. En el ejemplo hay dos:
creados: un contador compartido que cuenta cuántos alumnos se han creado (no tiene sentido guardarlo en cada alumno).notaValida: una función de utilidad que no necesita ningún alumno para funcionar.
En Python, un atributo de clase se declara en el cuerpo de la clase (creados = 0) y se usa con el nombre de la clase (Alumno.creados); un método estático lleva @staticmethod y no recibe self. Las funciones sueltas del módulo también sirven como utilidades.
No abuses de lo estático
Un atributo estático es estado global: cualquiera puede cambiarlo y los resultados dependen de lo que haya ocurrido antes en el programa, lo que complica las pruebas. Úsalo para contadores sencillos, constantes y utilidades; no para guardar los datos de la aplicación.
Errores frecuentes¶
| Error | Cómo evitarlo |
|---|---|
| Devolver la lista interna para que otros la modifiquen | Ofrece métodos concretos, o devuelve una copia o una vista de solo lectura |
Buscar el máximo empezando con un valor «inventado» (como 0) |
Empieza con el primer elemento (y comprueba que la lista no esté vacía) |
| Dividir por el tamaño de una lista vacía | Comprueba antes que hay elementos |
| Poner datos de objetos concretos en un atributo estático | Un atributo estático es para lo que es común a todos |
Para practicar¶
Las colecciones de objetos y las relaciones entre clases se practican en U4.3 · POO II. Pasar de uno a otro muestra las mismas ideas en otro lenguaje.