sobrecarga de operadores en Python ejercicios complejo polinomio chuletario

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.


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 real e imaginario con @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 + c funcione igual que c + 2
  • Operador unario - y función abs() — el módulo del complejo es √(a² + b²)
  • Método conjugado() — devuelve el complejo con la parte imaginaria negada
  • __str__ con formato a+bi o a-bi según el signo
  • __repr__ con formato Complejo(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__ para 2 * 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 a p.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 - p no es p - 3 — es Polinomio([3]) - p
  • Para __str__ trata por separado el término independiente, los lineales y los de grado > 1
  • La derivada de aₙxⁿ es n·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

Python operator overloading — exercises to master magic methods

Basic Level

Exercise 1 — Complex Number class

Implement a Complex class representing complex numbers a + bi. Private attributes real and imaginary with @property. Equality only, no ordering. Arithmetic: +, -, * between complex numbers and with real numbers. Reflected operators. Unary - and abs(). conjugate() method. __str__ as a+bi or a-bi. __repr__ as Complex(a, b).

💡 Hint — non-commutative reflected operators:

# WRONG — treats subtraction as commutative
def __rsub__(self, other):
    return self.__sub__(other)    # calculates self - other
                                  # should be other - self

# Check:
c = Complex(3, 4)
print(3 - c)    # should be 0-4i
                # with mistake gives 0+4i — wrong sign on imaginary

# RIGHT — invert the order
def __rsub__(self, other):
    if isinstance(other, (int, float)):
        return Complex(other - self.real, -self.imaginary)
    return NotImplemented

3 - c means 3 - (3+4i) = (3-3) + (0-4)i = 0-4i. Calculating as c - 3 gives 0+4i — opposite imaginary sign.

Intermediate and Challenge Level

Exercise 2 — Polynomial class

Coefficients stored in ascending degree order, [1, -3, 2] = 1 - 3x + 2x².

Basic: degree(), evaluate(x), __str__, __repr__. Intermediate: __add__, __sub__, __mul__ between polynomials and with scalars. Reflected operators. Challenge: __eq__, __neg__, __call__ (makes p(3) work like p.evaluate(3)), derivative().

💡 Hints: Pad the shorter polynomial with zeros before adding. For __mul__ use distributive property. The __rsub__ mistake: 3 - p is not p - 3 — it’s Polynomial([3]) - p. Derivative of aₙxⁿ is n·aₙxⁿ⁻¹.

(Solutions same as Spanish version above)

Cheat sheet — Python Operator Overloading

# ============================================
# CHEAT SHEET — Operator Overloading
# Sergio Learns · sergiolearns.com
# ============================================

# COMPARISON WITH @total_ordering
from functools import total_ordering

@total_ordering
class MyClass:
    def __eq__(self, other):
        if not isinstance(other, MyClass): return False
        return ...
    def __lt__(self, other):
        if not isinstance(other, MyClass): return NotImplemented
        return ...    # @total_ordering generates >, <=, >=

# ALL COMPARISON OPERATORS
# __eq__ → ==    __ne__ → != (auto if __eq__ defined)
# __lt__ → <     __gt__ → >
# __le__ → <=    __ge__ → >=

# ARITHMETIC — self OP other
# __add__      → +     __radd__     → + (reflected)
# __sub__      → -     __rsub__     → - (reflected)
# __mul__      → *     __rmul__     → * (reflected)
# __truediv__  → /     __rtruediv__ → / (reflected)

# CORRECT ARITHMETIC PATTERN
def __add__(self, other):
    if isinstance(other, MyClass): return MyClass(...)
    elif isinstance(other, (int, float)): return MyClass(...)
    return NotImplemented    # NEVER None

# REFLECTED OPERATORS — when self is on the RIGHT
# Python tries: type(a).__add__(a, b)
# If NotImplemented → type(b).__radd__(b, a)
# If NotImplemented → TypeError

# COMMUTATIVE — reflected same as direct
def __radd__(self, other): return self.__add__(other)
def __rmul__(self, other): return self.__mul__(other)

# NOT COMMUTATIVE — reflected inverted
def __rsub__(self, other):
    # other - self ≠ self - other
    if isinstance(other, (int, float)):
        return MyClass(other - self.value)    # inverted order
    return NotImplemented

def __rtruediv__(self, other):
    # other / self ≠ self / other
    if isinstance(other, (int, float)):
        return MyClass(other / self.value)    # inverted order
    return NotImplemented

# UNARY
def __neg__(self): return MyClass(-self.value)
def __abs__(self): return MyClass(abs(self.value))
def __round__(self, n=0): return MyClass(round(self.value, n))

# CONVERSION
# __int__   → int(obj)      __float__ → float(obj)
# __bool__  → bool(obj)     __call__  → obj(args)

# POLYMORPHISM WITHOUT REAL OVERLOADING
def method(self, other):
    if isinstance(other, MyClass): ...
    elif isinstance(other, int): ...
    else: return NotImplemented

# COMMON MISTAKES
# 1. return None instead of NotImplemented → breaks reflected ops
# 2. __rsub__ same as __sub__ → wrong result
# 3. __rtruediv__ same as __truediv__ → wrong result
# 4. No isinstance check → AttributeError
# 5. @total_ordering without __eq__ → doesn't work

# PRACTICAL RULE
# + * → commutative → __radd__ = __add__, __rmul__ = __mul__
# - / → NOT commutative → __rsub__ and __rtruediv__ inverted

# Python operator resolution for a OP b:
# 1. type(a).__op__(a, b)     → if NotImplemented:
# 2. type(b).__rop__(b, a)    → if NotImplemented:
# 3. TypeError

Publicaciones Similares

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *