Sobrecarga de operadores en Python — ejercicios para dominar los métodos mágicos
La sobrecarga de operadores en Python ejercicios con solución cierran este bloque. Ya viste la teoría y practicaste con clases reales. Ahora toca resolver por tu cuenta. Dos ejercicios en tres niveles con especial atención al error más traicionero, implementar __rsub__ o __rtruediv__ como si la operación fuera conmutativa cuando no lo es. Recuerda revisar tus erroes con pythontutor.com.
Tabla de Contenidos
Sobrecarga de operadores en Python ejercicios — Nivel Básico
Ejercicio 1 — Clase Número Complejo
Implementa una clase Complejo que represente números complejos de la forma a + bi. Un número complejo tiene parte real a y parte imaginaria b.
Requisitos:
- Atributos privados
realeimaginariocon@property - Operadores de comparación — dos complejos son iguales si tienen la misma parte real e imaginaria. No tiene sentido ordenarlos con
<o>— no los implementes - Operadores aritméticos:
+,-,*entre complejos y entre complejo y número real - Operadores reflejados para que
2 + cfuncione igual quec + 2 - Operador unario
-y funciónabs()— el módulo del complejo es√(a² + b²) - Método
conjugado()— devuelve el complejo con la parte imaginaria negada __str__con formatoa+bioa-bisegún el signo__repr__con formatoComplejo(a, b)
La salida debe ser:
c1 = 3+4i c2 = 1-2i repr: Complejo(3, 4) --- Aritmética --- c1 + c2 = 4+2i c1 - c2 = 2+6i c1 * c2 = 11-2i c1 + 2 = 5+4i 2 + c1 = 5+4i 3 - c1 = 0-4i ← __rsub__ — no conmutativo 2 * c1 = 6+8i --- Unarios y módulo --- -c1 = -3-4i abs(c1) = 5.0 c1.conjugado() = 3-4i --- Comparación --- c1 == Complejo(3, 4): True c1 == c2: False
💡 Pista — el error de los operadores reflejados no conmutativos:
# MAL — tratas la resta como si fuera conmutativa
def __rsub__(self, other):
return self.__sub__(other) # calcula self - other
# pero debería ser other - self
# Comprobación:
c = Complejo(3, 4)
print(3 - c) # debería ser 0-4i (3-3 + 0-4i)
# con el error da -0-4i (3-3 + 0-(-4)i) → MAL
# BIEN — inviertes el orden
def __rsub__(self, other):
if isinstance(other, (int, float)):
return Complejo(other - self.real, -self.imaginario)
return NotImplemented
La resta 3 - c significa 3 - (3+4i) = (3-3) + (0-4)i = 0-4i. Si lo calculas como c - 3 obtienes (3-3) + 4i = 0+4i — resultado opuesto en la parte imaginaria.
Sobrecarga de operadores en Python ejercicios — Nivel Intermedio y Desafío
Ejercicio 2 — Clase Polinomio
Implementa una clase Polinomio que represente un polinomio de una variable. Un polinomio se representa por sus coeficientes, la lista [2, -3, 1] representa 2x² - 3x + 1.
Requisitos:
Básico:
- Atributo privado
coeficientes— lista donde el índice es el grado del término grado()— devuelve el grado del polinomio (índice del coeficiente más alto no nulo)evaluar(x)— evalúa el polinomio en un punto__str__con formato legible:2x² - 3x + 1__repr__:Polinomio([2, -3, 1])
Intermedio:
__add__y__sub__entre polinomios y entre polinomio y número__radd__y__rsub__— recuerda que la resta no es conmutativa__mul__entre polinomios y entre polinomio y número (escalar)__rmul__para2 * p
Desafío:
__eq__— dos polinomios son iguales si tienen los mismos coeficientes (ignorando ceros al final)__neg__— niega todos los coeficientes__call__— permite usar el polinomio como función:p(3)equivale ap.evaluar(3)derivada()— devuelve el polinomio derivada
La salida debe ser:
p1 = 2x² - 3x + 1 p2 = x + 2 repr: Polinomio([1, -3, 2]) --- Evaluación --- p1(0) = 1 p1(1) = 0 p1(3) = 10 p2(2) = 4 --- Aritmética --- p1 + p2 = 2x² - 2x + 3 p1 - p2 = 2x² - 4x - 1 p1 * p2 = 2x³ + x² - 5x + 2 p1 * 2 = 4x² - 6x + 2 2 * p1 = 4x² - 6x + 2 3 - p1 = -2x² + 3x + 2 ← __rsub__ — no conmutativo --- Derivada --- p1' = 4x - 3 p2' = 1 --- Comparación --- p1 == Polinomio([1, -3, 2]): True
💡 Pistas:
- Guarda los coeficientes en orden ascendente de grado —
[1, -3, 2]=1 + (-3)x + 2x² - Para
__add__rellena con ceros el polinomio más corto antes de sumar - Para
__mul__entre polinomios usa la propiedad distributiva — cada término del primero multiplica a todos del segundo - El error de
__rsub__:3 - pno esp - 3— esPolinomio([3]) - p - Para
__str__trata por separado el término independiente, los lineales y los de grado > 1 - La derivada de
aₙxⁿesn·aₙxⁿ⁻¹
Soluciones Comentadas
Solución Ejercicio 1:
import math
from functools import total_ordering
class Complejo:
def __init__(self, real, imaginario=0):
if not isinstance(real, (int, float)):
raise TypeError('La parte real debe ser numérica')
if not isinstance(imaginario, (int, float)):
raise TypeError('La parte imaginaria debe ser numérica')
self.__real = float(real)
self.__imaginario = float(imaginario)
@property
def real(self):
return self.__real
@property
def imaginario(self):
return self.__imaginario
def conjugado(self):
return Complejo(self.__real, -self.__imaginario)
def modulo(self):
return math.sqrt(self.__real**2 + self.__imaginario**2)
# Comparación — solo igualdad, sin orden
def __eq__(self, other):
if isinstance(other, Complejo):
return (abs(self.__real - other.real) < 1e-9 and
abs(self.__imaginario - other.imaginario) < 1e-9)
elif isinstance(other, (int, float)):
return self.__real == other and self.__imaginario == 0
return False
# Aritmética
def __add__(self, other):
if isinstance(other, Complejo):
return Complejo(self.__real + other.real,
self.__imaginario + other.imaginario)
elif isinstance(other, (int, float)):
return Complejo(self.__real + other, self.__imaginario)
return NotImplemented
def __radd__(self, other):
return self.__add__(other) # suma ES conmutativa
def __sub__(self, other):
if isinstance(other, Complejo):
return Complejo(self.__real - other.real,
self.__imaginario - other.imaginario)
elif isinstance(other, (int, float)):
return Complejo(self.__real - other, self.__imaginario)
return NotImplemented
def __rsub__(self, other):
# other - self ≠ self - other
if isinstance(other, (int, float)):
return Complejo(other - self.__real, -self.__imaginario)
return NotImplemented
def __mul__(self, other):
if isinstance(other, Complejo):
# (a+bi)(c+di) = (ac-bd) + (ad+bc)i
real = self.__real * other.real - self.__imaginario * other.imaginario
imag = self.__real * other.imaginario + self.__imaginario * other.real
return Complejo(real, imag)
elif isinstance(other, (int, float)):
return Complejo(self.__real * other, self.__imaginario * other)
return NotImplemented
def __rmul__(self, other):
return self.__mul__(other) # multiplicación ES conmutativa
def __neg__(self):
return Complejo(-self.__real, -self.__imaginario)
def __abs__(self):
return self.modulo()
def __str__(self):
if self.__imaginario == 0:
return str(self.__real)
signo = '+' if self.__imaginario >= 0 else ''
real_str = f'{self.__real:.6g}'
imag_str = f'{self.__imaginario:.6g}'
return f'{real_str}{signo}{imag_str}i'
def __repr__(self):
return f'Complejo({self.__real}, {self.__imaginario})'
# Uso
c1 = Complejo(3, 4)
c2 = Complejo(1, -2)
print(f'c1 = {c1}')
print(f'c2 = {c2}')
print(f'repr: {repr(c1)}\n')
print('--- Aritmética ---')
print(f'c1 + c2 = {c1 + c2}')
print(f'c1 - c2 = {c1 - c2}')
print(f'c1 * c2 = {c1 * c2}')
print(f'c1 + 2 = {c1 + 2}')
print(f'2 + c1 = {2 + c1}')
print(f'3 - c1 = {3 - c1}')
print(f'2 * c1 = {2 * c1}\n')
print('--- Unarios y módulo ---')
print(f'-c1 = {-c1}')
print(f'abs(c1) = {abs(c1)}')
print(f'c1.conjugado() = {c1.conjugado()}\n')
print('--- Comparación ---')
print(f'c1 == Complejo(3, 4): {c1 == Complejo(3, 4)}')
print(f'c1 == c2: {c1 == c2}')
Solución Ejercicio 2:
class Polinomio:
def __init__(self, coeficientes):
if not coeficientes:
raise ValueError('El polinomio debe tener al menos un coeficiente')
# Eliminar ceros al final
self.__coef = list(coeficientes)
while len(self.__coef) > 1 and self.__coef[-1] == 0:
self.__coef.pop()
@property
def coeficientes(self):
return list(self.__coef)
def grado(self):
return len(self.__coef) - 1
def evaluar(self, x):
resultado = 0
for i, c in enumerate(self.__coef):
resultado += c * (x ** i)
return resultado
def __call__(self, x):
return self.evaluar(x)
def derivada(self):
if self.grado() == 0:
return Polinomio([0])
nuevos = [i * c for i, c in enumerate(self.__coef) if i > 0]
return Polinomio(nuevos)
# Comparación
def __eq__(self, other):
if isinstance(other, Polinomio):
return self.__coef == other.coeficientes
return False
# Aritmética
def __add__(self, other):
if isinstance(other, Polinomio):
n = max(len(self.__coef), len(other.coeficientes))
c1 = self.__coef + [0] * (n - len(self.__coef))
c2 = other.coeficientes + [0] * (n - len(other.coeficientes))
return Polinomio([a + b for a, b in zip(c1, c2)])
elif isinstance(other, (int, float)):
nuevos = list(self.__coef)
nuevos[0] += other
return Polinomio(nuevos)
return NotImplemented
def __radd__(self, other):
return self.__add__(other) # suma ES conmutativa
def __sub__(self, other):
if isinstance(other, Polinomio):
return self + (-other)
elif isinstance(other, (int, float)):
nuevos = list(self.__coef)
nuevos[0] -= other
return Polinomio(nuevos)
return NotImplemented
def __rsub__(self, other):
# other - self ≠ self - other
if isinstance(other, (int, float)):
return Polinomio([other]) - self
return NotImplemented
def __mul__(self, other):
if isinstance(other, Polinomio):
n = len(self.__coef) + len(other.coeficientes) - 1
resultado = [0] * n
for i, a in enumerate(self.__coef):
for j, b in enumerate(other.coeficientes):
resultado[i + j] += a * b
return Polinomio(resultado)
elif isinstance(other, (int, float)):
return Polinomio()
return NotImplemented
def __rmul__(self, other):
return self.__mul__(other) # multiplicación escalar ES conmutativa
def __neg__(self):
return Polinomio([-c for c in self.__coef])
def __str__(self):
if all(c == 0 for c in self.__coef):
return '0'
terminos = []
for i in range(len(self.__coef) - 1, -1, -1):
c = self.__coef[i]
if c == 0:
continue
if i == 0:
terminos.append(str(int(c) if c == int(c) else c))
elif i == 1:
if c == 1: terminos.append('x')
elif c == -1: terminos.append('-x')
else: terminos.append(f'{int(c) if c == int(c) else c}x')
else:
if c == 1: terminos.append(f'x{chr(8304+i) if i < 10 else f"^{i}"}')
elif c == -1: terminos.append(f'-x{chr(8304+i) if i < 10 else f"^{i}"}')
else:
exp = chr(8304 + i) if i < 10 else f'^{i}'
terminos.append(f'{int(c) if c == int(c) else c}x{exp}')
resultado = terminos[0]
for t in terminos[1:]:
if t.startswith('-'):
resultado += f' - {t[1:]}'
else:
resultado += f' + {t}'
return resultado
def __repr__(self):
return f'Polinomio({self.__coef})'
# Uso
p1 = Polinomio([1, -3, 2]) # 2x² - 3x + 1
p2 = Polinomio([2, 1]) # x + 2
print(f'p1 = {p1}')
print(f'p2 = {p2}')
print(f'repr: {repr(p1)}\n')
print('--- Evaluación ---')
print(f'p1(0) = {p1(0)}')
print(f'p1(1) = {p1(1)}')
print(f'p1(3) = {p1(3)}')
print(f'p2(2) = {p2(2)}\n')
print('--- Aritmética ---')
print(f'p1 + p2 = {p1 + p2}')
print(f'p1 - p2 = {p1 - p2}')
print(f'p1 * p2 = {p1 * p2}')
print(f'p1 * 2 = {p1 * 2}')
print(f'2 * p1 = {2 * p1}')
print(f'3 - p1 = {3 - p1}\n')
print('--- Derivada ---')
print(f"p1' = {p1.derivada()}")
print(f"p2' = {p2.derivada()}\n")
print('--- Comparación ---')
print(f'p1 == Polinomio([1, -3, 2]): {p1 == Polinomio([1, -3, 2])}')
Chuletario — Sobrecarga de operadores en Python
# ============================================
# CHULETARIO — Sobrecarga de operadores
# Sergio Learns · sergiolearns.com
# ============================================
# COMPARACIÓN CON @total_ordering
from functools import total_ordering
@total_ordering
class MiClase:
def __eq__(self, other):
if not isinstance(other, MiClase): return False
return ... # comparación real
def __lt__(self, other):
if not isinstance(other, MiClase): return NotImplemented
return ... # @total_ordering genera >, <=, >=
# TODOS LOS OPERADORES DE COMPARACIÓN
# __eq__ → == __ne__ → != (auto si defines __eq__)
# __lt__ → < __gt__ → >
# __le__ → <= __ge__ → >=
# ARITMÉTICA BINARIA — self OP other
# __add__ → + __radd__ → + (reflejado)
# __sub__ → - __rsub__ → - (reflejado)
# __mul__ → * __rmul__ → * (reflejado)
# __truediv__ → / __rtruediv__ → / (reflejado)
# __floordiv__ → // __mod__ → %
# __pow__ → **
# PATRÓN ARITMÉTICO CORRECTO
def __add__(self, other):
if isinstance(other, MiClase):
return MiClase(...)
elif isinstance(other, (int, float)):
return MiClase(...)
return NotImplemented # NUNCA None
# OPERADORES REFLEJADOS — cuando self está a la DERECHA
# Python prueba: type(a).__add__(a, b)
# Si NotImplemented → type(b).__radd__(b, a)
# Si NotImplemented → TypeError
# CONMUTATIVOS — reflejado igual al directo
def __radd__(self, other): return self.__add__(other)
def __rmul__(self, other): return self.__mul__(other)
# NO CONMUTATIVOS — reflejado invertido
def __rsub__(self, other):
# other - self ≠ self - other
if isinstance(other, (int, float)):
return MiClase(other - self.valor) # orden invertido
return NotImplemented
def __rtruediv__(self, other):
# other / self ≠ self / other
if isinstance(other, (int, float)):
return MiClase(other / self.valor) # orden invertido
return NotImplemented
# UNARIOS
# __neg__ → -obj
# __pos__ → +obj
# __abs__ → abs(obj)
# __round__ → round(obj, n)
# __invert__ → ~obj
def __neg__(self): return MiClase(-self.valor)
def __abs__(self): return MiClase(abs(self.valor))
def __round__(self, n=0): return MiClase(round(self.valor, n))
# CONVERSIÓN
# __int__ → int(obj)
# __float__ → float(obj)
# __bool__ → bool(obj)
# __str__ → str(obj) / print(obj)
# __repr__ → repr(obj)
# __len__ → len(obj)
# __call__ → obj(args) — hace el objeto llamable como función
def __call__(self, x):
return self.evaluar(x) # obj(x) equivale a obj.evaluar(x)
# CONTENEDORES
# __len__ → len(obj)
# __getitem__ → obj[key]
# __setitem__ → obj[key] = value
# __contains__ → x in obj
# __iter__ → for x in obj
# POLIMORFISMO SIN SOBRECARGA REAL
# Parámetros por omisión
def __init__(self, param1, param2=None):
if param2 is None:
... # comportamiento con un parámetro
else:
... # comportamiento con dos parámetros
# Por tipo de parámetro
def metodo(self, other):
if isinstance(other, MiClase): ...
elif isinstance(other, int): ...
elif isinstance(other, float): ...
else: return NotImplemented
# ERRORES TÍPICOS
# 1. return None en vez de NotImplemented → rompe los reflejados
# 2. __rsub__ igual que __sub__ → resultado incorrecto
# 3. __rtruediv__ igual que __truediv__ → resultado incorrecto
# 4. No comprobar isinstance → AttributeError al acceder a atributos
# 5. @total_ordering sin __eq__ → no funciona
# 6. Comparar con == sin isinstance → puede comparar referencias
# FLUJO COMPLETO Python con a OP b
# 1. type(a).__op__(a, b) → si NotImplemented:
# 2. type(b).__rop__(b, a) → si NotImplemented:
# 3. TypeError: unsupported operand type(s)
# REGLA PRÁCTICA
# + * → conmutativos → __radd__ = __add__, __rmul__ = __mul__
# - / → NO conmutativos → __rsub__ y __rtruediv__ invertidos
Sigue aprendiendoTodos los artículos de Python¿Te ha servido este artículo? Así puedes apoyar SergioLearns →

Un comentario