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 clases te permiten modificar o mejorar clases envolviéndolas con otra función. Funcionan como los decoradores de funciones, pero se aplican a clases enteras.
Aquí tienes una clase sencilla sin decorar:
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 método nuevo:
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 clsAplica 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 tanto los métodos originales como los añadidos:
person = EnhancedPerson("Alice")
print(person.greet()) # Hola, me llamo Alice
print(person.farewell()) # Adiós de parte de AliceTambién puedes envolver un método existente: guardar el original para que aún puedas llamarlo dentro del contenedor. Esto es ú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 # Guarda el método original
def tracked_greet(self):
print(f"greet was called") # Comportamiento adicional previo
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, primero se ejecuta el envoltorio y después delega en el original:
person = TrackedPerson("Alice")
print(person.greet())
# Se llamó a greet
# Hola, me llamo AliceLos 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) más 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 ofrece una forma clara de ampliar la funcionalidad de una clase sin usar herencia.
Desafío
IntermedioEn este desafío, implementarás un decorador de clase que añade la funcionalidad de seguimiento de llamadas a métodos.
decorator.py: implementa el decorador de claseadd_countercalculator.py: contiene las clases que usarán tu decorador
El archivo driver.py contiene amplios escenarios de prueba 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
# Gestor completo 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) # Debería imprimir {'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) # Debería imprimir {'add': 2, 'subtract': 1}
print(calc2.call_counts) # Debería imprimir {'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) # Debería imprimir {'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) # Debería imprimir {'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) # Debería imprimir {'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) # Debería imprimir {'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) # Debería imprimir {'add': 1, 'subtract': 1}
elif test_case == "counter_reset":
calc = Calculator()
print(calc.add(5, 3)) # Debería imprimir 8
print(calc.call_counts) # Debería imprimir {'add': 1, 'subtract': 0}
# Restablece el contador
calc.call_counts = {"add": 0, "subtract": 0}
print(calc.add(2, 7)) # Debería imprimir 9
print(calc.call_counts) # Debería imprimir {'add': 0, 'subtract': 0}
elif test_case == "counter_manipulation":
calc = Calculator()
print(calc.add(5, 3)) # Debería imprimir 8
print(calc.call_counts) # Debería imprimir {'add': 1, 'subtract': 0}
# Modifica el contador directamente
calc.call_counts["add"] = 100
print(calc.add(2, 7)) # Debería imprimir 9
print(calc.call_counts) # Debería imprimir {'add': 101, 'subtract': 0}
elif test_case == "performance_test":
calc = Calculator()
# Realiza 1000 operaciones de add
for i in range(1000):
calc.add(i, i+1)
# Realiza 500 operaciones de subtract
for i in range(500):
calc.subtract(i+10, i)
print(calc.call_counts) # Debería imprimir {'add': 1000, 'subtract': 500}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
1Fundamentos de la POO
Introducción a la POOArchivos externosClases vs. objetosEl parámetro selfMétodosAtributosMétodo constructor (__init__)Repaso: calculadora sencilla4Herencia
Herencia básicaLa función super()Sobrescritura de métodosHerencia múltipleOrden de resolución de métodosRepaso: jerarquía de empleados7Métodos especiales
Introducción a los métodos mágicosSobrecarga de operadoresMétodos mágicos de contenedoresRepaso: lista personalizada10Patrones de diseño, parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón Strategy2Decoradores
Introducción a los decoradoresDecorador de propiedadDecorador de método estáticoDecorador de método de clase5Polimorfismo
Repaso de la sobrescritura de métodosTipado patoClases y métodos abstractosDiseño de interfacesRepaso: calculadora de formas8Conceptos avanzados de POO
Composición frente a herenciaMixinsMétodos estáticos y de claseDecoradores de claseGestores de contexto11Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón Composite3Propiedades de clase
Variables de instancia vs. variables de claseDecoradores de propiedadesAtributos privadosRepaso: gestor de cuentas bancariasPractica por tu cuenta: Compilador de Python online