Juan Diego Andrés PRADA··RAMÍREZ Entrar
Lección 5 de 7

Composición y dunder methods

¿Qué es?

La composición consiste en construir objetos a partir de otros objetos: una clase contiene instancias de otras clases como atributos. Es la relación "tiene un": un Curso tiene estudiantes; un Estudiante tiene calificaciones.

Los dunder methods (de double underscore, doble guion bajo: __init__, __str__, __len__...) son métodos especiales que Python invoca automáticamente cuando usas sintaxis nativa sobre tus objetos. Definirlos hace que tus clases se comporten como tipos integrados.

¿Cómo funciona?

En composición, simplemente guardas otros objetos dentro de self (por ejemplo, una lista de Estudiante). Para los dunder, defines métodos con nombres reservados: __str__ controla qué muestra print(obj); __repr__, la representación técnica; __len__, qué devuelve len(obj); __eq__, cómo se comparan dos objetos con ==; __lt__, cómo se ordenan con < (lo que usa sorted).

¿Para qué sirve?

La composición sirve para modelar relaciones del mundo real y para mantener cada clase enfocada en una responsabilidad. Los dunder sirven para que tu objeto sea natural de usar: len(curso), print(estudiante), sorted(estudiantes) funcionan sin métodos raros.

Qué pieza del sistema construimos

Construimos la clase Curso, que contiene una lista de estudiantes (composición), y le damos dunder methods para que len(curso) cuente estudiantes, print(curso) muestre un resumen legible y sorted(curso.estudiantes) los ordene por promedio. Es el ensamblaje que une todas las piezas anteriores.

Composición: un Curso que contiene estudiantes

Asumimos la clase Estudiante de los módulos previos (con nombre y la propiedad promedio).

class Curso:
 def __init__(self, nombre):
 self.nombre = nombre
 self.estudiantes = [] # el Curso "tiene" estudiantes

 def inscribir(self, estudiante):
 self.estudiantes.append(estudiante)

 @property
 def promedio_general(self):
 if not self.estudiantes:
 return 0.0
 return sum(e.promedio for e in self.estudiantes) / len(self.estudiantes)

El Curso no hereda de Estudiante: lo contiene. Esta es la diferencia clave entre composición ("tiene un") y herencia ("es un"). Un curso no es un estudiante; tiene estudiantes.

Regla práctica para decidir: si dudas entre herencia y composición, prefiere composición. "El curso tiene estudiantes" es composición; "el becado es un estudiante" es herencia. Si la frase natural es "tiene", compón.

Dunder methods: que el objeto hable Python

__str__ y __repr__

class Estudiante:
 def __init__(self, nombre):
 self.nombre = nombre
 self.notas = []

 @property
 def promedio(self):
 return sum(self.notas) / len(self.notas) if self.notas else 0.0

 def __str__(self):
 return f"{self.nombre} (promedio {self.promedio:.2f})"

 def __repr__(self):
 return f"Estudiante({self.nombre!r})"

__str__ es para humanos (lo que ve print); __repr__ es para programadores (lo que ves en la consola o dentro de una lista, ideal para depurar). Define al menos __repr__.

__len__, __eq__ y __lt__

class Curso:
 def __init__(self, nombre):
 self.nombre = nombre
 self.estudiantes = []

 def inscribir(self, estudiante):
 self.estudiantes.append(estudiante)

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

 def __str__(self):
 return f"Curso {self.nombre}: {len(self)} estudiantes"

Agregamos a Estudiante la comparación y el orden:

 def __eq__(self, otro):
 return isinstance(otro, Estudiante) and self.nombre == otro.nombre

 def __lt__(self, otro):
 return self.promedio < otro.promedio

Con __lt__ definido, sorted() ya sabe ordenar estudiantes por promedio.

Todo junto

curso = Curso("POO")
ana = Estudiante("Ana"); ana.notas += [4.0, 5.0]
luis = Estudiante("Luis"); luis.notas += [3.0, 3.5]
curso.inscribir(ana)
curso.inscribir(luis)

print(curso) # Curso POO: 2 estudiantes
print(len(curso)) # 2
print(sorted(curso.estudiantes)) # [Estudiante('Luis'), Estudiante('Ana')]
for e in curso.estudiantes:
 print(e) # usa __str__ de Estudiante

sorted muestra a Luis primero (promedio menor) gracias a __lt__, y cada uno se representa con __repr__ dentro de la lista.

Error común: definir __eq__ y olvidar __hash__. Si haces tu objeto comparable con __eq__, deja de ser usable como clave de diccionario o en un set salvo que definas también __hash__. Para objetos que no cambian, puedes añadir __hash__ = object.__hash__ o derivarlo de los mismos campos que usa __eq__.

Tech English: composition = composición, dunder = doble guion bajo, representation = representación (__repr__), has-a / is-a = tiene-un / es-un.

Ejercicios

  1. Curso indexable. Diseña en Curso el dunder __getitem__ para que curso[0] devuelva el primer estudiante inscrito. Indica qué otro dunder necesitarías para que for e in curso: recorra los estudiantes directamente.

  2. Comparar calificaciones. Diseña __eq__ y __lt__ en la clase Calificacion (del módulo de encapsulamiento) para poder ordenar una lista de notas con sorted. Justifica sobre qué campo comparas.

Tu progreso se guarda en este navegador. Inicia sesión para guardarlo en tu cuenta y verlo desde cualquier dispositivo.