Menu
Coddy logo textTech

Decoradores de clase

Parte de la sección Programación orientada a objetos del Journey de Python de Coddy. Lección 40 de 64.

Los decoradores de clase te permiten modificar o mejorar clases envolviéndolas con otra función. Funcionan como los decoradores de función, pero se aplican a clases enteras.

Aquí tienes una clase sencilla sin decoración:

class Person:
    def __init__(self, name):
        self.name = name
        
    def greet(self):
        return f"Hello, my name is {self.name}"

Crea un decorador de clase que añada un nuevo método:

def add_farewell(cls):
    def farewell(self):
        return f"Goodbye from {self.name}"
    
    cls.farewell = farewell  # Añade el método a la clase
    return cls

Aplica el decorador a una clase usando @:

@add_farewell
class EnhancedPerson:
    def __init__(self, name):
        self.name = name
        
    def greet(self):
        return f"Hello, my name is {self.name}"

Ahora la clase tiene métodos originales y añadidos:

person = EnhancedPerson("Alice")
print(person.greet())     # Hola, mi nombre es Alice
print(person.farewell())  # Adiós de Alice

También puedes envolver un método existente: guardar el original para poder seguir llamándolo dentro del envoltorio. Esto resulta útil cuando quieres añadir comportamiento (como seguimiento o registro) alrededor de un método que la clase ya tiene:

def add_tracking(cls):
    original_greet = cls.greet  # Almacena el método original

    def tracked_greet(self):
        print(f"greet was called")   # Comportamiento extra antes
        return original_greet(self)  # Llama al método original

    cls.greet = tracked_greet  # Reemplaza el método en la clase
    return cls

@add_tracking
class TrackedPerson:
    def __init__(self, name):
        self.name = name

    def greet(self):
        return f"Hello, my name is {self.name}"

Cuando llamas a greet ahora, el contenedor se ejecuta primero y después delega en el original:

person = TrackedPerson("Alice")
print(person.greet())
# greet fue llamado
# Hola, mi nombre es Alice

Los pasos clave para envolver un método existente son:
1. Guarda el método original: original = cls.method
2. Define una nueva función que llame a original(self) además de cualquier lógica adicional.
3. Asigna la nueva función de nuevo a la clase: cls.method = new_function
4. Devuelve la clase modificada.

Punto clave: los decoradores de clase reciben una clase como entrada, la modifican o mejoran y devuelven la clase modificada. Pueden añadir métodos, modificar atributos o envolver la funcionalidad existente. Usa @decorator_name encima de la definición de la clase para aplicarlos. Esto proporciona una forma clara de ampliar la funcionalidad de una clase sin herencia.

challenge icon

Desafío

Intermedio

En este desafío, implementarás un decorador de clase que añade funcionalidad de seguimiento de llamadas a métodos.

  • decorator.py - Implementa el decorador de clase add_counter
  • calculator.py - Contiene las clases que usarán tu decorador

El archivo driver.py contiene escenarios de prueba exhaustivos que validarán tu implementación con diversos casos de uso, patrones de herencia y casos límite. No necesitas modificar este archivo, pero examinarlo te ayudará a comprender el comportamiento esperado.

Pruébalo tú mismo

from calculator import Calculator

# Manejador integral de casos de prueba
test_case = input()

if test_case == "basic_functionality":
    calc = Calculator()
    print(calc.add(5, 3))  # Debería imprimir 8
    print(calc.add(2, 7))  # Debería imprimir 9
    print(calc.subtract(10, 4))  # Debería imprimir 6
    print(calc.call_counts)  # Should print {'add': 2, 'subtract': 1}

elif test_case == "multiple_instances":
    calc1 = Calculator()
    calc2 = Calculator()
    
    print(calc1.add(5, 3))  # Debería imprimir 8
    print(calc2.add(2, 7))  # Debería imprimir 9
    print(calc1.subtract(10, 4))  # Debería imprimir 6
    
    # Ambas instancias deberían compartir el mismo call_counts
    print(calc1.call_counts)  # Should print {'add': 2, 'subtract': 1}
    print(calc2.call_counts)  # Should print {'add': 2, 'subtract': 1}
    print(calc1.call_counts is calc2.call_counts)  # Debería imprimir True

elif test_case == "method_call_order":
    calc = Calculator()
    
    print(calc.add(1, 2))  # Debería imprimir 3
    print(calc.subtract(5, 2))  # Debería imprimir 3
    print(calc.add(10, 20))  # Debería imprimir 30
    print(calc.add(7, 3))  # Debería imprimir 10
    print(calc.call_counts)  # Should print {'add': 3, 'subtract': 1}

elif test_case == "large_values":
    calc = Calculator()
    
    print(calc.add(1000000, 2000000))  # Debería imprimir 3000000
    print(calc.subtract(5000000, 2000000))  # Debería imprimir 3000000
    print(calc.call_counts)  # Should print {'add': 1, 'subtract': 1}

elif test_case == "negative_values":
    calc = Calculator()
    
    print(calc.add(-5, -3))  # Debería imprimir -8
    print(calc.subtract(-10, -4))  # Debería imprimir -6
    print(calc.call_counts)  # Should print {'add': 1, 'subtract': 1}

elif test_case == "float_values":
    calc = Calculator()
    
    print(calc.add(5.5, 3.2))  # Debería imprimir 8.7
    print(calc.subtract(10.5, 4.2))  # Debería imprimir 6.3
    print(calc.call_counts)  # Should print {'add': 1, 'subtract': 1}

elif test_case == "zero_values":
    calc = Calculator()
    
    print(calc.add(0, 0))  # Debería imprimir 0
    print(calc.subtract(0, 0))  # Debería imprimir 0
    print(calc.call_counts)  # Should print {'add': 1, 'subtract': 1}

elif test_case == "counter_reset":
    calc = Calculator()
    
    print(calc.add(5, 3))  # Debería imprimir 8
    print(calc.call_counts)  # Should print {'add': 1, 'subtract': 0}
    
    # Reinicia el contador
    calc.call_counts = {"add": 0, "subtract": 0}
    
    print(calc.add(2, 7))  # Debería imprimir 9
    print(calc.call_counts)  # Should print {'add': 0, 'subtract': 0}

elif test_case == "counter_manipulation":
    calc = Calculator()
    
    print(calc.add(5, 3))  # Debería imprimir 8
    print(calc.call_counts)  # Should print {'add': 1, 'subtract': 0}
    
    # Manipula el contador directamente
    calc.call_counts["add"] = 100
    
    print(calc.add(2, 7))  # Debería imprimir 9
    print(calc.call_counts)  # Should print {'add': 101, 'subtract': 0}

elif test_case == "performance_test":
    calc = Calculator()
    
    # Realiza 1000 operaciones add
    for i in range(1000):
        calc.add(i, i+1)
    
    # Realiza 500 operaciones subtract
    for i in range(500):
        calc.subtract(i+10, i)
    
    print(calc.call_counts)  # Should print {'add': 1000, 'subtract': 500}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación orientada a objetos

Practica por tu cuenta: Compilador de Python online