Introduzione

Non ci soffermeremo in questi esempi sulla terminologia relativa alla programmazione orientata agli oggetti, dandola per nota. Ci concentreremo su alcuni esempi di base e su eventuali differenze tra Python e altri linguaggi di programmazione.

Un primo esempio di classe

In un primo esempio, utilizzeremo una classe solo per memorizzarci dei dati relativi ad un certo oggetto (come in una struct del C o in un record del Pascal).

Il concetto chiave, qui, è che i "campi" sono gli elementi di un dizionario automaticamente predisposto, il cui nome è __dict__, per cui possiamo utilizzare le normali operazioni sui dizionari per visualizzarli.

I "campi" vengono chiamati anche "proprietà" o "attributi" dell'oggetto (con alcune piccole sfumature nel significato).

Come al solito, presentiamo il codice inframmezzato dai risultati dell'esecuzione:

class Picture():
    pass
    # si usa la parola chiave "pass" quando
    # non c'è un corpo della funzione o della classe

img1=Picture()
img1.filename="gatto.jpg"
img1.filesize=12340
img1.width=320
img1.height=240
img1.filetype='JPEG'

print("Visualizzazione dell'intero dizionario:")
print(img1.__dict__)

Visualizzazione dell'intero dizionario:
{'width': 320, 'filetype': 'JPEG', 'height': 240, 'filesize': 12340, 'filename': 'gatto.jpg'}

img1.width=480
img1.__dict__['width']=480  # equivalente alla precedente
print("Visualizzazione dell'intero dizionario (un valore è cambiato):")
print(img1.__dict__)

Visualizzazione dell'intero dizionario (un valore è cambiato):
{'width': 480, 'filetype': 'JPEG', 'height': 240, 'filesize': 12340, 'filename': 'gatto.jpg'}

print("Visualizzazione delle chiavi:")
for key in img1.__dict__.keys():
    print(key)

Visualizzazione delle chiavi:
width
filetype
height
filesize
filename

print("Visualizzazione dei valori:")
for value in img1.__dict__.values():
    print(value)

Visualizzazione dei valori:
480
JPEG
240
12340
gatto.jpg

print("Visualizzazione delle coppie chiave/valore:")
for key, value in img1.__dict__.items():
    print(key, ' -->', value)

Visualizzazione delle coppie chiave/valore:
width  --> 480
filetype  --> JPEG
height  --> 240
filesize  --> 12340
filename  --> gatto.jpg

Il costruttore

Quando viene istanziato un nuovo oggetto della nostra classe, viene automaticamente invocata una funzione membro (detta anche "metodo") speciale, detto costruttore. In Python, tale funzione ha come nome __init__(). (Vedremo più avanti che in realtà la creazione di un'istanza in Python è effettuata in due momenti). Usando il costruttore, possiamo, ad esempio, impostare dei valori di default per alcuni dei valori dei "campi" (o, meglio, di quello che abbiamo visto essere il dizionario degli attributi):

class Picture():
    def __init__(self, filename, width=320, height=240, filetype='JPEG'):
        self.filename= filename
        self.width=width
        self.height=height
        self.filetype=filetype

img1=Picture('gatto.jpg', height=480)

print("Visualizzazione dell'intero dizionario:")
print(img1.__dict__)

Output:

Visualizzazione dell'intero dizionario:
{'width': 320, 'filetype': 'JPEG', 'height': 480, 'filename': 'gatto.jpg'}

Come per tutti i metodi della classe, per motivi che vedremo in seguito, in Python il primo parametro deve essere un riferimento all'oggetto stesso (che tradizionalmente viene chiamato self).

Setters e getters

Con il codice presentato qui sopra, non c'è nulla che possa evitare di utilizzare nomi non validi per assegnarli a particolari attributi. Ad esempio, sarebbe perfettamente lecito, per quanto sconveniente, scrivere istruzioni del tipo

img1.width = "abc"
img1.height = -12

Per ovviare a questo problema, può essere una buona idea racchiudere il codice da usare per assegnare un valore ad uno degli attributi in una funzione speciale, in modo da poter fare tutti i controlli che desideriamo.

L'attributo diventa quindi "privato" (cioè, non accessibile direttamente dall'esterno). In Python gli attributi "privati" sono indicati con nomi che iniziano con un doppio segno di sottolineatura. (È importante notare che in Python tali attributi non sono realmente privati, così come invece in C++, visto che in casi particolari — ad esempio per esigenze di debug — è comunque possibile accedere ai valori.)

Esempio di setter

Nell'esempio che segue, sono stati resi "privati" gli attributi width e height, e sono state definiti due metodi per poter cambiare loro il valore.

class Picture():
    def __init__(self, filename, width=320, height=240, filetype='JPEG'):
        self.filename = filename
        self.setWidth(width)
        self.setHeight(height)
        self.filetype=filetype

    def setWidth(self, value):
        assert(isinstance(value, int))
        assert(value>=0)
        self.__width = value

    def setHeight(self, value):
        assert(isinstance(value, int))
        assert(value>=0)
        self.__height = value

img1=Picture('gatto.jpg', height=480)

img1.setWidth(200)
img1.__width = -10 # crea un nuovo attributo, non fa accedere a quello privato

print("Visualizzazione dell'intero dizionario:")
print(img1.__dict__)

Output:

Visualizzazione dell'intero dizionario:
{'_Picture__height': 480, '__width': -10, 'filetype': 'JPEG', '_Picture__width': 200, 'filename': 'gatto.jpg'}

Esempio di getter

Alla nostra classe, per i campi width e height, possiamo aggiungere anche dei metodi getter, che ci consentono di accedere agli attributi privati. Inoltre, già che ci siamo, aggiungiamo un metodo che fornisce una rappresentazione completa dell'oggetto:

class Picture():
    ....

    def getWidth(self):
        return self.__width

    def getHeight(self):
        return self.__height

    def getCompleteDescription(self):
        text = "Immagine %s, larghezza %d, altezza %d" % (
            self.filename,
            self.getWidth(),
            self.getHeight()
            )
        return text

...

print(img1.getCompleteDescription())

Output:

Immagine gatto.jpg, larghezza 200, altezza 480

Più avanti, vedremo che — grazie al decoratore @property — saremo in grado di definire un accesso agli attributi anche senza il richiamo esplicito della funzione, ma in maniera più naturale.

Cosa restituisce un setter?

Una funzione che imposta il valore di un attributo, svolto il suo compito, normalmente non deve restituire un valore o un riferimento. Se però si fa in modo che restituisca un riferimento all'oggetto stesso (self), sarà possibile usare una sintassi più concisa per richiamare più metodi sullo stesso oggetto:

def setWidth(self, value):
    assert(isinstance(value, int))
    assert(value>=0)
    self.__width = value
    return self

def setHeight(self, value):
    assert(isinstance(value, int))
    assert(value>=0)
    self.__height = value
    return self

Questo ci consente di implementare, se lo desideriamo, una interfaccia fluente, grazie alla quale, se vogliamo impostare sia la larghezza sia l'altezza dell'immagine, potremo scrivere:

img1.setWidth(2000).setHeight(3000)

Il riferimeno a self nei parametri dei metodi

Un chiarimento rispetto all'uso della parola chiave self nella lista dei parametri dei metodi può essere utile. Immaginiamo di avere una classe definita in questo modo:

class Picture():
    def doSomething(self, n):
        print("Devo fare qualcosa con il valore %d." % n)

Nel momento in cui vogliamo usare il metodo doSomething() abbiamo due possibilità, perfettamente equivalenti.

La prima è di usare il nome dell'istanza:

img1=Picture()
img1.doSomething(42)   # uso il nome dell'istanza

La seconda è di usare il nome della classe:

Picture.doSomething(img1, 42)  # uso il nome della classe,
                               # e passo l'istanza come primo parametro

Il primo modo è più pratico, ma viene comunque tradotto internamente in una chiamata al metodo della classe Picture, passando il riferimento all'istanza indicata come primo parametro. Nel codice del metodo doSomething() non c'è modo di sapere come si chiama l'istanza (ce ne potrebbero essere molte), quindi si usa un riferimento generico, che è appunto self.

Tra l'altro, il nome self è semplicemente una convenzione. Sarebbe perfettamente lecito, seppur sconsigliabile per ovvii motivi di leggibilità, scrivere un codice come il seguente:

class Picture():
    def setDescription(foo, text):
        foo.description=text

    def getDescription(bar):
        return bar.description

img1=Picture()
img1.setDescription('nice picture of the sea')
print(img1.getDescription())

results matching ""

    No results matching ""