lettura facile

Il metodo __setattr__() in Python

Oggi parliamo del metodo `__setattr__()` in Python. Questo metodo viene chiamato automaticamente ogni volta che assegni un valore a un attributo di un oggetto.

oggetto.__setattr__('attributo', valore)

Fa parte dei cosiddetti metodi magici o speciali del linguaggio Python.

Come funziona?

Quando assegni un valore a un attributo di un oggetto, il sistema chiama automaticamente il metodo `__setattr__()` dell'oggetto e lo esegue.

Ad esempio, quando scrivi qualcosa come:

obj.attr = valore

Dietro le quinte Python sta facendo qualcosa di simile a:

obj.__setattr__('attr', valore)

Quindi `__setattr__()` ha il compito di dire all'oggetto come deve reagire quando qualcuno cerca di cambiare uno dei suoi attributi.

Nota. Python ha già un comportamento predefinito per questo metodo, che semplicemente imposta il valore dell'attributo come ci si aspetterebbe. Ma quello che è veramente utile è che ti permette di ridefinire questo metodo per personalizzare il comportamento sugli oggetti di una particolare classe. In altre parole, puoi dire a Python: "Ehi, ogni volta che qualcuno prova a cambiare un attributo di questo oggetto, voglio che succeda qualcos'altro".

Un esempio pratico

Definisci una semplice classe Persona.

class Persona:
    def __init__(self, nome, eta):
        self.nome = nome
        self.eta = eta

Poi genera un'istanza della classe

p = Persona("Luca", 20)

Quando modifichi il valore di un attributo dell'oggetto, Python cerca ed esegue il metodo predefinito `__setattr__()`

p.eta=21

Ora nell'attributo 'eta' c'è il valore 21

print(p.eta)

21

Puoi ottenere lo stesso risultato anche chiamando direttamente il metodo `__setattr__()` dell'oggetto

p.__setattr__('eta', 22)

Questo comando attribuisce il valore 22 all'attributo 'eta' dell'oggetto 'p'.

Il risultato finale è sempre lo stesso, hai modificato il valore dell'attributo.

print(p.eta)

22

Fin qui hai usato il metodo `__setattr__()` predefinito di Python, nel prossimo paragrafo vediamo come personalizzarlo.

Come personalizzare il metoto __setattr__() di una classe      

Il linguaggio Python ti permette di sovrascrivere il metodo `__setattr__()` di una classe tramite l'overriding.

In questo modo puoi personalizzare il comportamento del programma quando modifichi il valore di un attributo di un oggetto della classe.

Ad esempio, immagina di voler creare una classe che tiene traccia di tutti i cambiamenti fatti agli attributi dell'oggetto.

class TracciatoreModifiche:
    def __init__(self):
        # Creiamo un dizionario per tenere traccia dei cambiamenti
        self.modifiche = {}

    def __setattr__(self, nome, valore):
        # Prima di tutto, bisogna controllare se stiamo tentando di cambiare l'attributo 'modifiche' stesso,
        # altrimenti si entra in un ciclo infinito.

        if nome == 'modifiche':
            super().__setattr__(nome, valore)
        else:
            # Aggiungiamo la modifica al nostro dizionario
            self.modifiche[nome] = valore
            # Poi utilizziamo il comportamento predefinito per settare l'attributo
            super().__setattr__(nome, valore)

Crea un'istanza della classe

t = TracciatoreModifiche()

Ora modifica degli attributi

t.nome = "Andrea"
t.età = 42

Vediamo lo storico delle modifiche apportate all'oggetto 't'

print(t.modifiche)

{'nome': 'Andrea', 'età': 42}

Cosa sta succedendo qui?

Ogni volta che assegniamo un valore a un attributo (ad esempio `t.nome = "Andrea"`), Python chiama `__setattr__()` e  aggiunge il nuovo valore al dizionario `modifiche`.

Poi utilizza la versione "base" del metodo, cioè `super().__setattr__()`, per evitare di entrare in un ciclo infinito e far sì che l'attributo venga effettivamente assegnato all'oggetto.

Così facendo, puoi tener traccia di tutte le modifiche fatte agli attributi senza interferire con il normale funzionamento della classe.

Perché non possiamo assegnare direttamente? Un punto cruciale da comprendere qui è che dentro `__setattr__()`, non puoi assegnare direttamente gli attributi usando la sintassi normale, tipo:

self.nome = valore

Questo porterebbe Python a richiamare di nuovo `__setattr__()` in un ciclo infinito. Per evitare questo, usiamo la versione super() del metodo che salta la nostra versione personalizzata di `__setattr__()` e richiama direttamente la versione base, evitando quindi il loop.

super().__setattr__(nome, valore)`

Ti faccio un altro esempio di utilizzo del metodo.

Puoi utilizzare `__setattr__()` anche nella validazione degli attributi.

Ad esempio, immagina ora di voler limitare il tipo di dati che può essere assegnato agli attributi di una classe.

class Persona:
    def __init__(self, nome=None, età=None):
        # Inizializziamo gli attributi per evitare errori
        self.nome = nome
        self.età = età

    def __setattr__(self, nome, valore):
        if nome == "età" and (not isinstance(valore, int) or valore < 0):
            raise ValueError("L'età deve essere un intero positivo.")
        super().__setattr__(nome, valore)

In questa classe l'attributo `età` accetta solo valori interi positivi.

Questa regola è stata implementata tramite il metodo `__setattr__()`.

Crea una istanza della classe

p = Persona()

Poi assegna degli attributi.

p.nome = "Andrea"
p.età = 42 

Il programma funziona correttamente perché 42 è un numero intero positivo.

print(p.eta)

42

Tuttavia, se provi ad assegnare un valore negativo all'attributo 'eta', il programma genera un'eccezione.

p.età = -5 

ValueError: L'età deve essere un intero positivo.

Anche in questo caso ricorda però di usare `super().__setattr__()` per assegnare i valori agli attributi all'interno del metodo `__setattr__()`, altrimenti rischi di entrare in loop infiniti!

Spero che questo ti dia una chiara idea del perché `__setattr__()` sia così utile e come puoi utilizzarlo nella pratica.




Se qualcosa non ti è chiaro, scrivi la tua domanda nei commenti.




FacebookTwitterLinkedinLinkedin