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.

Decoradores de classe permitem que você modifique ou aprimore classes ao envolvê-las 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

Aplique o decorador 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 classe possui tanto os métodos originais quanto os adicionados:

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 você ainda possa chamá-lo dentro do wrapper. Isso é útil quando você deseja adicionar comportamento (como rastreamento ou log) em torno de um método que a classe 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 executa primeiro e depois delega para o original:

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

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

Ponto Chave: Decoradores de classe recebem uma classe como entrada, a modificam ou aprimoram e retornam a classe modificada. Eles podem adicionar métodos, modificar atributos ou envolver funcionalidades existentes. Use @decorator_name acima da definição da classe para aplicá-los. Isso fornece uma maneira limpa de estender a funcionalidade da classe sem herança.

challenge icon

Desafio

Médio

Neste desafio, você implementará um decorador de classe que adiciona a 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 extensivos que validarão sua implementação em relação a vários casos de uso, padrões de herança e casos de borda. Você não precisa modificar este arquivo, mas examiná-lo ajudará você a entender o comportamento esperado.

Folha de consulta

Decoradores de classe modificam ou aprimoram classes ao envolvê-las com outra função. Eles recebem uma classe como entrada, a modificam e retornam a classe modificada.

Sintaxe básica de um decorador de classe:

def decorator_name(cls):
    # Modify the class
    return cls

@decorator_name
class MyClass:
    pass

Exemplo - adicionando um método a uma classe:

def add_farewell(cls):
    def farewell(self):
        return f"Goodbye from {self.name}"
    
    cls.farewell = farewell  # Add the method to the class
    return cls

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

person = EnhancedPerson("Alice")
print(person.greet())     # Hello, my name is Alice
print(person.farewell())  # Goodbye from Alice

Exemplo - envolvendo um método existente (armazenar o original e depois substituir):

def loud_greet(cls):
    original_greet = cls.greet  # Store the original method

    def new_greet(self):
        result = original_greet(self)  # Call the original
        return result.upper()          # Enhance the result

    cls.greet = new_greet  # Replace with the wrapped version
    return cls

@loud_greet
class EnhancedPerson:
    def __init__(self, name):
        self.name = name

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

person = EnhancedPerson("Alice")
print(person.greet())  # HELLO, MY NAME IS ALICE

Passos principais para envolver um método existente:

original = cls.method_name — salva o método original
def new_method(self): original(self) — chama-o dentro do wrapper
cls.method_name = new_method — substitui o método na classe

Decoradores de classe podem adicionar métodos, modificar atributos ou envolver funcionalidades existentes sem usar herança.

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)  # Deve imprimir {'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)  # Deve imprimir {'add': 2, 'subtract': 1}
    print(calc2.call_counts)  # Deve imprimir {'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)  # Deve imprimir {'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)  # Deve imprimir {'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)  # Deve imprimir {'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)  # Deve imprimir {'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)  # Deve imprimir {'add': 1, 'subtract': 1}

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

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

elif test_case == "performance_test":
    calc = Calculator()
    
    # Realizar 1000 operações de adição
    for i in range(1000):
        calc.add(i, i+1)
    
    # Realizar 500 operações de subtração
    for i in range(500):
        calc.subtract(i+10, i)
    
    print(calc.call_counts)  # Deve imprimir {'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