Python - Wrapper Classes (Italiano)

Introduzione alle Classi Wrapper

Ciao a tutti, futuri maghi Python! Oggi, inizieremo un avventuroso viaggio nel mondo delle classi wrapper. Non preoccupatevi se siete nuovi nella programmazione – vi guiderò passo per passo attraverso questo concetto, proprio come ho fatto per innumerevoli studenti durante gli anni della mia docenza.

Python - Wrapper Classes

Immagina di avere un bellissimo regalo, ma di volerlo rendere ancora più speciale incartandolo in un papello elegante. Questo è essenzialmente ciò che facciamo con le classi wrapper in Python – prendiamo oggetti esistenti e li "incartiamo" con funzionalità aggiuntive. Cool, vero?

Cos'sono le Classi Wrapper?

Una classe wrapper è una classe che circonda (o "incarta") un oggetto di un'altra classe o un tipo di dati primitivo. È come mettere una custodia protettiva sul vostro smartphone – il telefono funziona ancora allo stesso modo, ma ora ha alcune funzionalità e protezione aggiuntive.

Perché Utilizzare le Classi Wrapper?

  1. Per aggiungere nuove funzionalità agli oggetti esistenti
  2. Per modificare il comportamento dei metodi esistenti
  3. Per controllare l'accesso all'oggetto originale

Diamo un'occhiata a alcuni esempi di codice per vedere come funziona tutto questo nella pratica!

Esempio di Classe Wrapper di Base

class StringWrapper:
def __init__(self, string):
self.string = string

def get_string(self):
return self.string

def append(self, text):
self.string += text

# Utilizzo del nostro wrapper
wrapped_string = StringWrapper("Ciao")
print(wrapped_string.get_string())  # Output: Ciao
wrapped_string.append(" Mondo!")
print(wrapped_string.get_string())  # Output: Ciao Mondo!

In questo esempio, abbiamo creato una semplice classe wrapper per le stringhe. Rompiamoci il gioco:

  1. Definiamo una classe chiamata StringWrapper.
  2. Il metodo __init__ inizializza il nostro wrapper con una stringa.
  3. get_string() ci permette di recuperare la stringa incartata.
  4. append() è un nuovo metodo che aggiunge funzionalità – aggiunge testo alla nostra stringa.

Visto come abbiamo aggiunto una nuova funzionalità (append) a una stringa di base? Questa è la potenza delle classi wrapper!

Modifica del Comportamento con le Classi Wrapper

Ora, diamo un'occhiata a come possiamo modificare il comportamento dei metodi esistenti:

class ShoutingList(list):
def __getitem__(self, index):
return super().__getitem__(index).upper()

# Utilizzo del nostro wrapper
normal_list = ["ciao", "mondo", "python"]
shouting_list = ShoutingList(normal_list)

print(normal_list[0])     # Output: ciao
print(shouting_list[0])   # Output: CIAO

In questo esempio:

  1. Creiamo una classe ShoutingList che eredita dalla classe built-in list.
  2. Sovrascriviamo il metodo __getitem__ per restituire stringhe in maiuscolo.
  3. Quando accediamo agli elementi nella nostra ShoutingList, questi vengono automaticamente convertiti in maiuscolo.

È come avere un amico che urla sempre quando ripete cosa dite – stesso contenuto, diversa consegna!

Controllo dell'Accesso con le Classi Wrapper

Le classi wrapper possono anche essere utilizzate per controllare l'accesso all'oggetto originale. Questo è particolarmente utile per la protezione dei dati o per implementare oggetti in sola lettura:

class ReadOnlyWrapper:
def __init__(self, data):
self._data = data

def get_data(self):
return self._data

def __setattr__(self, name, value):
if name == '_data':
super().__setattr__(name, value)
else:
raise AttributeError("Questo oggetto è di sola lettura")

# Utilizzo del nostro wrapper
data = [1, 2, 3]
read_only_data = ReadOnlyWrapper(data)

print(read_only_data.get_data())  # Output: [1, 2, 3]
read_only_data.get_data().append(4)  # Questo funziona, modifica la lista originale
print(read_only_data.get_data())  # Output: [1, 2, 3, 4]

try:
read_only_data.new_attribute = "Non posso aggiungere questo"
except AttributeError as e:
print(e)  # Output: Questo oggetto è di sola lettura

In questo esempio:

  1. Creiamo una classe ReadOnlyWrapper che permette solo la lettura dei dati.
  2. Sovrascriviamo __setattr__ per impedire l'aggiunta di nuovi attributi al wrapper.
  3. I dati originali possono ancora essere modificati tramite get_data(), ma non possono essere aggiunti nuovi attributi al wrapper stesso.

È come avere una mostra museale – puoi guardare, ma non puoi toccare!

Applicazioni Pratiche delle Classi Wrapper

Le classi wrapper hanno numerose applicazioni nel mondo reale. Ecco alcuni esempi:

  1. Logging: Incartare oggetti per registrare chiamate di metodi o accessi agli attributi.
  2. Caching: Implementare un livello di caching attorno a operazioni costose.
  3. Validazione dell'input: Aggiungere controlli per assicurarsi che i dati soddisfino determinati criteri prima di essere utilizzati.
  4. Lazy loading: Posticipare la creazione di un oggetto fino a quando non è effettivamente necessario.

Implementiamo un semplice wrapper di logging:

import time

class LoggingWrapper:
def __init__(self, obj):
self.wrapped_obj = obj

def __getattr__(self, name):
original_attr = getattr(self.wrapped_obj, name)
if callable(original_attr):
def wrapper(*args, **kwargs):
start_time = time.time()
result = original_attr(*args, **kwargs)
end_time = time.time()
print(f"Chiamato {name}, ci è voluto {end_time - start_time:.2f} secondi")
return result
return wrapper
return original_attr

# Utilizzo del nostro logging wrapper
class SlowCalculator:
def add(self, x, y):
time.sleep(1)  # Simula un'operazione lenta
return x + y

calc = SlowCalculator()
logged_calc = LoggingWrapper(calc)

result = logged_calc.add(3, 4)
print(f"Risultato: {result}")

Output:

Chiamato add, ci è voluto 1.00 secondi
Risultato: 7

In questo esempio:

  1. Creiamo un LoggingWrapper che incarta qualsiasi oggetto.
  2. Intercetta le chiamate dei metodi, registra il tempo impiegato e poi chiama il metodo originale.
  3. Lo utilizziamo per incartare un oggetto SlowCalculator e registrare le sue chiamate di metodo.

È come avere un assistente personale che times tutte le tue attività e ti riporta i risultati!

Conclusione

Le classi wrapper sono uno strumento potente in Python che consente di estendere, modificare e controllare gli oggetti in modi flessibili. Sono come leArmy knives svizzere della programmazione orientata agli oggetti – versatili e incredibilmente utili nelle giuste situazioni.

Ricorda, la chiave per padroneggiare le classi wrapper è la pratica. Prova a creare i tuoi wrapper per diversi oggetti e vedi come puoi migliorare la loro funzionalità. Chi sa? Potresti finire per incartare il tuo cammino verso diventare un maestro Python!

Buon coding, e che il tuo codice sia sempre ben incartato! ??

Credits: Image by storyset