Menu
Coddy logo textTech

Decoradores de Classe

Parte da seção Object Oriented Programming do Journey de Python da Coddy. Lição 40 de 64.

Os decoradores de class permitem modificar ou aprimorar classes envolvendo-as com outra função. Eles funcionam como decoradores de função, mas são aplicados a classes inteiras.

Aqui está uma classe simples sem decoração:

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

Crie um decorador de classe que adiciona um novo método:

def add_farewell(cls):
    def farewell(self):
        return f"Goodbye from {self.name}"
    
    cls.farewell = farewell  # Adiciona o método à classe
    return cls

Apply o decorator a uma classe usando @:

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

Agora, a class tem both original e added methods:

person = EnhancedPerson("Alice")
print(person.greet())     # Olá, meu nome é Alice
print(person.farewell())  # Adeus de Alice

Você também pode envolver um método existente: armazenando o original para que ainda possa chamá-lo dentro do wrapper. Isso é útil quando você quer adicionar behaviour (como rastreamento ou registro) ao redor de um método que a class já possui:

def add_tracking(cls):
    original_greet = cls.greet  # Armazena o método original

    def tracked_greet(self):
        print(f"greet was called")   # Comportamento extra antes
        return original_greet(self)  # Chama o método original

    cls.greet = tracked_greet  # Substitui o método na classe
    return cls

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

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

Quando você chama greet agora, o wrapper é executado primeiro e, em seguida, delega para o original:

person = TrackedPerson("Alice")
print(person.greet())
# greet foi chamado
# Olá, meu nome é Alice

As principais etapas para envolver um método existente são:
1. Salve o método original: original = cls.method
2. Defina uma nova função que chame original(self) além de qualquer lógica extra.
3. Atribua a nova função novamente à classe: cls.method = new_function
4. Retorne a classe modificada.

Ponto-chave: Os decoradores de class recebem uma class como input, modificam ou aprimoram-na e return a class modified. Eles podem add methods, modificar atributos ou envolver functionality existente. Use @decorator_name acima da class definition para aplicá-los. Isso oferece uma maneira limpa de estender a functionality da class sem herança.

challenge icon

Desafio

Médio

Neste desafio, você implementará um decorador de classe que adiciona funcionalidade de rastreamento de chamadas de métodos.

  • decorator.py - Implemente o decorador de classe add_counter
  • calculator.py - Contém as classes que usarão seu decorador

O arquivo driver.py contém cenários de teste abrangentes que validarão sua implementação em relação a vários casos de uso, padrões de herança e casos extremos. Você não precisa modificar este arquivo, mas examiná-lo ajudará você a entender o comportamento esperado.

Experimente você mesmo

from calculator import Calculator

# Manipulador abrangente de casos de teste
test_case = input()

if test_case == "basic_functionality":
    calc = Calculator()
    print(calc.add(5, 3))  # Deve imprimir 8
    print(calc.add(2, 7))  # Deve imprimir 9
    print(calc.subtract(10, 4))  # Deve 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))  # Deve imprimir 8
    print(calc2.add(2, 7))  # Deve imprimir 9
    print(calc1.subtract(10, 4))  # Deve imprimir 6
    
    # Ambas as instâncias devem compartilhar o mesmo 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)  # Deve imprimir True

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

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

elif test_case == "negative_values":
    calc = Calculator()
    
    print(calc.add(-5, -3))  # Deve imprimir -8
    print(calc.subtract(-10, -4))  # Deve 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))  # Deve imprimir 8.7
    print(calc.subtract(10.5, 4.2))  # Deve 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))  # Deve imprimir 0
    print(calc.subtract(0, 0))  # Deve imprimir 0
    print(calc.call_counts)  # Should print {'add': 1, 'subtract': 1}

elif test_case == "counter_reset":
    calc = Calculator()
    
    print(calc.add(5, 3))  # Deve imprimir 8
    print(calc.call_counts)  # Should print {'add': 1, 'subtract': 0}
    
    # Reseta o contador
    calc.call_counts = {"add": 0, "subtract": 0}
    
    print(calc.add(2, 7))  # Deve imprimir 9
    print(calc.call_counts)  # Should print {'add': 0, 'subtract': 0}

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

elif test_case == "performance_test":
    calc = Calculator()
    
    # Realiza 1000 operações de add
    for i in range(1000):
        calc.add(i, i+1)
    
    # Realiza 500 operações de subtract
    for i in range(500):
        calc.subtract(i+10, i)
    
    print(calc.call_counts)  # Should print {'add': 1000, 'subtract': 500}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Object Oriented Programming

Pratique por conta própria: Compilador de Python online