Clases en Python — Parte II 🔧

Comparación, operaciones, contenedores, privacidad, propiedades y herencia

🔧 Taller de Artesanos

En esta parte implementamos comparación, aritmética, contenedores personalizados, miembros privados por convención, propiedades y herencia (incluida la clase base object y la sobrescritura de métodos).

7) Comparar objetos (__eq__, __lt__, etc.)

class Punto:
    def __init__(self, x, y):
        self.x = x; self.y = y

    def __eq__(self, other):
        return isinstance(other, Punto) and (self.x, self.y) == (other.x, other.y)

    def __lt__(self, other):
        return (self.x, self.y) < (other.x, other.y)

print(Punto(1,2) == Punto(1,2))
print(Punto(0,5) < Punto(1,0))

Nota: implementar lo necesario según el orden total o parcial deseado.

8) Operaciones aritméticas (__add__, __sub__)

class Vector:
    def __init__(self, x, y):
        self.x = x; self.y = y

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    def __sub__(self, other):
        return Vector(self.x - other.x, self.y - other.y)

v = Vector(1,2) + Vector(3,4)
w = Vector(5,5) - Vector(2,1)
print((v.x, v.y), (w.x, w.y))

Criterio: retornar nuevas instancias (inmutabilidad semántica del resultado).

9) Contenedores personalizados (__len__, __getitem__)

class Coleccion:
    def __init__(self, datos):
        self._datos = list(datos)

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

    def __getitem__(self, idx):
        return self._datos[idx]

c = Coleccion([10, 20, 30])
print(len(c), c[1])

Idea: implementar protocolos hace que el objeto sea interoperable con Python (for, slicing, etc.).

10) Miembros “privados”: _interno y __mangle

class Caja:
    def __init__(self, valor):
        self._valor = valor      # convención: “protegido”
        self.__clave = "secreto" # name mangling: _Caja__clave

    def abrir(self, clave):
        return clave == self.__clave

c = Caja(42)
print(c._valor, c._Caja__clave)   # se puede acceder, pero es mala práctica

Nota: Python confía en convenciones más que en restricciones fuertes.

11) Propiedades (@property)

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

    @property
    def nombre(self):
        return self._nombre

    @nombre.setter
    def nombre(self, nuevo):
        if not nuevo.strip():
            raise ValueError("Nombre vacío no permitido")
        self._nombre = nuevo

p = Persona("Ana")
p.nombre = "Ana María"
print(p.nombre)

Ventaja: interfaz de atributo con validación/cómputo bajo el capó.

12-14) Herencia y sobrescritura

class Animal:                     # hereda de object implícitamente
    def hablar(self):
        return "..."

class Perro(Animal):              # herencia simple
    def hablar(self):             # sobrescritura
        return "Guau"

class Gato(Animal):
    def hablar(self):
        return "Miau"

for a in (Perro(), Gato(), Animal()):
    print(a.hablar())

Punto clave: la clase base object está en la raíz del MRO; la sobrescritura permite comportamiento especializado.

💭 Checkpoint (reflexión)

  1. ¿Qué protocolos debes implementar para que tu clase “se comporte” como contenedor?
  2. ¿Qué aporta @property frente a exponer atributos públicos sin control?
  3. ¿Cómo decide Python qué método llamar en herencia? (pista: MRO)