lettura facile

Il metodo __get__() in Python

Il metodo `__get__()` in Python viene chiamato ogni volta che accedi a un attributo che usa un descrittore.

Cos'è un descrittore? In Python, un descrittore (o descriptor) è qualsiasi oggetto che implementa uno o più dei metodi speciali per definire come accedere, assegnare o cancellare un attributo di una classe. E tra questi metodi speciali, quello che ci interessa oggi è proprio `__get__()`.

Lo scopo principale del metodo `__get__()` è personalizzare ciò che viene restituito quando chiedi l'attributo.

Puoi decidere tu cosa restituire, come restituirlo e persino fare qualche trasformazione o controllo extra prima di dare il risultato.

La sintassi generale di questo metodo è la seguente:

def __get__(self, instance, owner):

Dove

  • `self`: si riferisce all'istanza del descriptor.
  • `instance`: è l'istanza della classe che contiene l'attributo (o `None` se stai accedendo all'attributo dalla classe stessa).
  • `owner`: è la classe che contiene il descriptor.

Ti faccio un semplice esempio per vedere in pratica cosa fa `__get__()`.

Supponi di voler gestire automaticamente l'accesso agli attributi in modo da formattare i dati ogni volta che vengono letti, come ad esempio una stringa con la prima lettera sempre maiuscola.

In questo caso devi creare un descrittore che formatta automaticamente il testo con la prima lettera maiuscola

class CapitalizeDescriptor:
    def __get__(self, instance, owner):
        # Recuperiamo il valore dell'attributo dall'istanza usando un attributo "privato"
        value = instance.__dict__.get(f"_{self.attribute}", "")
        return value.capitalize() if isinstance(value, str) else value

    def __set_name__(self, owner, name):
        # Salviamo il nome dell'attributo
        self.attribute = name

Questo descriptor si chiama 'CapitalizeDescriptor'.

Il metodo '__set_name__' salva il nome dell'attributo passato al descriptor mentre il metodo '__get__'  lo restituisce con la prima lettera maiuscola quando viene richiesto.

Ora immagina di avere una classe 'Person' che rappresenta una persona.

class Person:
    name = CapitalizeDescriptor()  # Usiamo il descriptor per l'attributo 'name'

    def __init__(self, name, eta):
        self._name = name
        self.eta = eta
        self.city = city

In questa classe l'attributo 'name' richiama il descrittore 'CapitalizeDescriptor'.

Questo significa che, ogni volta che accedi al nome, questo viene sempre formattato con la prima lettera maiuscola, indipendentemente da come viene inserito.

Nota che per evitare di sovrascrivere l'attributo 'name' nel costruttore __init__() bypassando il descriptor, l'attributo viene salvato nell'oggetto come attributo privato '_name'. Questo escamotage permette al descriptor di gestirlo correttamente.

Crea un'istanza della classe 'Person' passandogli una stringa composta da lettere minuscole.

p = Person("andrea", 23, 'milano')

Accedi all'attributo 'name' dell'oggetto

print(p.name) 

Quando tenti di accedere all'attributo, Python accede al metodo '__get__()' del descrittore 'CapitalizeDescriptor' che trasforma in maiuscola la prima lettera della stringa.

Andrea

In questo modo, ogni volta che accedi all'attributo `name`, ottieni una versione formattata del nome, indipendentemente da come lo abbiamo inserito.

Ovviamente questo avviene solo sull'attributo 'name' e non sugli altri. Ad esempio, se stampi il valore dell'attributo 'city', questo non passa per il descrittore e viene restituito in minuscolo.

print(p.output)

milano

Per fare in modo che entrambi gli attributi 'name' e 'city' passino per il descrittore e vengano restituiti con la prima lettera maiuscola, devi modificare la classe 'Person' in questo modo:

class Person:
    name = CapitalizeDescriptor()
    city = CapitalizeDescriptor()

    def __init__(self, name, eta):
        self._name = name
        self.eta = eta
        self._city = city

In conclusione, il metodo `__get__()` ti permette di controllare come accedere agli attributi degli oggetti di una classe. È il cuore dei descriptors e ti dà un livello di controllo che non avresti con semplici variabili di classe o di istanza.

Sperimenta un po' con il codice e vedrai quanto possono essere utili e flessibili i descrittori.




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




FacebookTwitterLinkedinLinkedin