Menu
Coddy logo textTech

Decorator di classe

Fa parte della sezione Programmazione orientata agli oggetti del percorso Python di Coddy. Lezione 40 di 64.

I decoratori di classe ti consentono di modificare o migliorare le classi avvolgendole con un'altra funzione. Funzionano come i decoratori di funzione, ma vengono applicati a intere classi.

Ecco una semplice classe senza decoratore:

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

Crea un decoratore di classe che aggiunga un nuovo metodo:

def add_farewell(cls):
    def farewell(self):
        return f"Goodbye from {self.name}"
    
    cls.farewell = farewell  # Aggiungi il metodo alla classe
    return cls

Applica il decoratore a una classe usando @:

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

Ora la classe ha sia i metodi originali sia quelli aggiunti:

person = EnhancedPerson("Alice")
print(person.greet())     # Ciao, mi chiamo Alice
print(person.farewell())  # Un saluto da Alice

Puoi anche avvolgere un metodo esistente: memorizza l'originale così puoi continuare a chiamarlo all'interno del wrapper. È utile quando vuoi aggiungere un comportamento (come il monitoraggio o la registrazione) attorno a un metodo già presente nella classe:

def add_tracking(cls):
    original_greet = cls.greet  # Memorizza il metodo originale

    def tracked_greet(self):
        print(f"greet was called")   # Comportamento aggiuntivo prima della chiamata
        return original_greet(self)  # Chiama il metodo originale

    cls.greet = tracked_greet  # Sostituisci il metodo nella 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 chiami greet ora, il wrapper viene eseguito per primo, poi delega all’originale:

person = TrackedPerson("Alice")
print(person.greet())
# greet è stato chiamato
# Ciao, mi chiamo Alice

I passaggi fondamentali per avvolgere un metodo esistente sono:
1. Salva il metodo originale: original = cls.method
2. Definisci una nuova funzione che chiama original(self) più eventuale logica aggiuntiva.
3. Riassegna la nuova funzione alla classe: cls.method = new_function
4. Restituisci la classe modificata.

Punto chiave: i decoratori di classe prendono una classe come input, la modificano o la potenziano e restituiscono la classe modificata. Possono aggiungere metodi, modificare attributi o racchiudere funzionalità esistenti. Usa @decorator_name sopra la definizione della classe per applicarli. Questo offre un modo semplice per estendere le funzionalità di una classe senza ricorrere all’ereditarietà.

challenge icon

Sfida

Medio

In questa sfida implementerai un decoratore di classe che aggiunge la funzionalità di tracciamento delle chiamate ai metodi.

  • decorator.py - Implementa il decoratore di classe add_counter
  • calculator.py - Contiene le classi che useranno il tuo decoratore

Il file driver.py contiene numerosi scenari di test che verificheranno la tua implementazione con vari casi d'uso, modelli di ereditarietà e casi limite. Non devi modificare questo file, ma esaminarlo ti aiuterà a capire il comportamento previsto.

Provalo tu

from calculator import Calculator

# Gestore completo dei casi di test
test_case = input()

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

elif test_case == "multiple_instances":
    calc1 = Calculator()
    calc2 = Calculator()
    
    print(calc1.add(5, 3))  # Dovrebbe stampare 8
    print(calc2.add(2, 7))  # Dovrebbe stampare 9
    print(calc1.subtract(10, 4))  # Dovrebbe stampare 6
    
    # Entrambe le istanze dovrebbero condividere lo stesso call_counts
    print(calc1.call_counts)  # Dovrebbe stampare {'add': 2, 'subtract': 1}
    print(calc2.call_counts)  # Dovrebbe stampare {'add': 2, 'subtract': 1}
    print(calc1.call_counts is calc2.call_counts)  # Dovrebbe stampare True

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

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

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

elif test_case == "float_values":
    calc = Calculator()
    
    print(calc.add(5.5, 3.2))  # Dovrebbe stampare 8.7
    print(calc.subtract(10.5, 4.2))  # Dovrebbe stampare 6.3
    print(calc.call_counts)  # Dovrebbe stampare {'add': 1, 'subtract': 1}

elif test_case == "zero_values":
    calc = Calculator()
    
    print(calc.add(0, 0))  # Dovrebbe stampare 0
    print(calc.subtract(0, 0))  # Dovrebbe stampare 0
    print(calc.call_counts)  # Dovrebbe stampare {'add': 1, 'subtract': 1}

elif test_case == "counter_reset":
    calc = Calculator()
    
    print(calc.add(5, 3))  # Dovrebbe stampare 8
    print(calc.call_counts)  # Dovrebbe stampare {'add': 1, 'subtract': 0}
    
    # Reimposta il contatore
    calc.call_counts = {"add": 0, "subtract": 0}
    
    print(calc.add(2, 7))  # Dovrebbe stampare 9
    print(calc.call_counts)  # Dovrebbe stampare {'add': 0, 'subtract': 0}

elif test_case == "counter_manipulation":
    calc = Calculator()
    
    print(calc.add(5, 3))  # Dovrebbe stampare 8
    print(calc.call_counts)  # Dovrebbe stampare {'add': 1, 'subtract': 0}
    
    # Modifica direttamente il contatore
    calc.call_counts["add"] = 100
    
    print(calc.add(2, 7))  # Dovrebbe stampare 9
    print(calc.call_counts)  # Dovrebbe stampare {'add': 101, 'subtract': 0}

elif test_case == "performance_test":
    calc = Calculator()
    
    # Esegui 1000 operazioni add
    for i in range(1000):
        calc.add(i, i+1)
    
    # Esegui 500 operazioni subtract
    for i in range(500):
        calc.subtract(i+10, i)
    
    print(calc.call_counts)  # Dovrebbe stampare {'add': 1000, 'subtract': 500}
quiz iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Compilatore Python online