Salve a tutti ragazzi, oggi introdurremo i concetti di classe e di oggetti. Prestate molta attenzione anche qui, perché introdurremo nuove keyword, e una nuova sintassi che a prima vista può non sembrare intuitiva.
Classi e oggetti
Tipi composti definiti dall’utente
Abbiamo usato alcuni dei tipi composti predefiniti e ora siamo pronti per crearne uno tutto nostro: il tipo Punto.
Considerando il concetto matematico di punto nelle due dimensioni, il punto è definito da una coppia di numeri (le coordinate). In notazione matematica le coordinate dei punti sono spesso scritte tra parentesi con una virgola posta a separare i due valori. Per esempio (0,0) rappresenta l’origine e (x,y) il punto che si trova x unità a destra e y unità in alto rispetto all’origine.
Un modo naturale di rappresentare un punto in Python è una coppia di numeri in virgola mobile e la questione che ci rimane da definire è in che modo raggruppare questa coppia di valori in un oggetto composto: un sistema veloce anche se poco elegante sarebbe l’uso di una tupla, anche se possiamo fare di meglio.
Un modo alternativo è quello di definire un nuovo tipo composto chiamato classe. Questo tipo di approccio richiede un pò di sforzo iniziale, ma i suoi benefici saranno subito evidenti.
Una definizione di classe ha questa sintassi:
class Punto: pass
Le definizioni di classe possono essere poste in qualsiasi punto di un programma ma solitamente per questioni di leggibilità sono poste all’inizio, subito sotto le istruzioni import. Le regole di sintassi per la definizione di una classe sono le stesse degli altri tipi composti: la definizione dell’esempio crea una nuova classe chiamata Punto. L’istruzione pass non ha effetti: è stata usata per il solo fatto che la definizione prevede un corpo che deve ancora essere scritto.
Creando la classe Punto abbiamo anche creato un nuovo tipo di dato chiamato con lo stesso nome. I membri di questo tipo sono detti istanze del tipo o oggetti. La creazione di una nuova istanza è detta istanziazione: solo al momento dell’istanziazione parte della memoria è riservata per depositare il valore dell’oggetto. Per creare un oggetto di tipo Punto viene chiamata una funzione chiamata Punto:
P1 = Punto()
Alla variabile P1 è assegnato il riferimento ad un nuovo oggetto Punto. Una funzione come Punto, che crea nuovi oggetti e riserva quindi della memoria per depositarne i valori, è detta costruttore.
Attributi
Possiamo aggiungere un nuovo dato ad un’istanza usando la notazione punto:
>>> P1.x = 3.0 >>> P1.y = 4.0
Questa sintassi è simile a quella usata per la selezione di una variabile appartenente ad un modulo, tipo math.pi e string.uppercase. In questo caso stiamo selezionando una voce da un’istanza e queste voci che fanno parte dell’istanza sono dette attributi.
Questo diagramma di stato mostra il risultato delle assegnazioni:
P1 : x–>3.0
y–>4.0
La variabile P1 si riferisce ad un oggetto Punto che contiene due attributi ed ogni attributo (una coordinata) si riferisce ad un numero in virgola mobile.
Possiamo leggere il valore di un attributo con la stessa sintassi:
>>> print P1.y 4.0 >>> x = P1.x >>> print x 3.0
L’espressione P1.x significa “vai all’oggetto puntato da P1 e ottieni il valore del suo attributo x”. In questo caso assegniamo il valore ad una variabile chiamata x: non c’è conflitto tra la variabile locale x e l’attributo x di P1: lo scopo della notazione punto è proprio quello di identificare la variabile cui ci si riferisce evitando le ambiguità.
Potete usare la notazione punto all’interno di ogni espressione così che le istruzioni proposte di seguito sono a tutti gli effetti perfettamente lecite:
print ’(’ + str(P1.x) + ’, ’ + str(P1.y) + ’)’ DistanzaAlQuadrato = P1.x * P1.x + P1.y * P1.y
La prima riga stampa (3.0, 4.0); la seconda calcola il valore 25.0. Potreste essere tentati di stampare direttamente il valore di P1:
>>> print P1 <__main__.Punto instance at 80f8e70>
Il risultato indica che P1 è un’istanza della classe Punto e che è stato definito in main . 80f8e70 è l’identificatore univoco dell’oggetto, scritto in base 16 (esadecimale). Probabilmente questo non è il modo più pratico di mostrare un oggetto Punto ma vedrete subito come renderlo più comprensibile.
Istanze come parametri
Puoi passare un’istanza come parametro ad una funzione nel solito modo:
def StampaPunto(Punto): print ’(’ + str(Punto.x) + ’, ’ + str(Punto.y) + ’)’
StampaPunto prende un oggetto Punto come argomento e ne stampa gli attributi in forma standard. Se chiamate StampaPunto(P1) la stampa è (3.0, 4.0)
Rettangoli
Se volessimo creare una classe per rappresentare un rettangolo quali informazioni dovremmo fornire per specificarlo in modo univoco? Per rendere le cose più semplici partiremo con un rettangolo orientato lungo gli assi.
Ci sono poche possibilità tra cui scegliere: potremmo specificare il centro del rettangolo e le sue dimensioni (altezza e larghezza); oppure specificare un angolo di riferimento e le dimensioni (ancora altezza e larghezza); o ancora specificare le coordinate di due punti opposti. Una scelta convenzionale abbastanza comune è quella di specificare il punto in alto a sinistra e le dimensioni.
Definiamo la nuova classe:
class Rettangolo: pass
Per istanziare un nuovo oggetto rettangolo:
Rett = Rettangolo() Rett.Larghezza = 100.0 Rett.Altezza = 200.0
Questo codice crea un nuovo oggetto Rettangolo con due attributi in virgola mobile. Ci manca solo il punto di riferimento in alto a sinistra e per specificarlo possiamo inserire un oggetto all’interno di un altro oggetto:
Rett.AltoSinistra = Punto() Rett.AltoSinistra.x = 0.0; Rett.AltoSinistra.y = 0.0;
L’operatore punto `e usato per comporre l’espressione: Rett.AltoSinistra.x significa “vai all’oggetto cui si riferisce Rett e seleziona l’attributo chiama- to AltoSinistra; poi vai all’oggetto cui si riferisce AltoSinistra e seleziona l’attributo chiamato x.”
Istanze come valori di ritorno
Le funzioni possono ritornare istanze. Possiamo quindi scrivere una funzione TrovaCentro che prende un oggetto Rettangolo come argomento e restituisce un oggetto Punto che contiene le coordinate del centro del rettangolo:
def TrovaCentro(Rettangolo): P = Punto() P.x = Rettangolo.AltoSinistra.x + Rettangolo.Larghezza/2.0 P.y = Rettangolo.AltoSinistra.y + Rettangolo.Altezza/2.0 return P
Per chiamare questa funzione passa Rett come argomento e assegna il risultato ad una variabile:
>>> Centro = TrovaCentro(Rett) >>> StampaPunto(Centro) (50.0, 100.0)
Gli oggetti sono mutabili
Possiamo cambiare lo stato di un oggetto facendo un’assegnazione ad uno dei suoi attributi. Per fare un esempio possiamo cambiare le dimensioni di Rett:
Rett.Larghezza = Rett.Larghezza + 50 Rett.Altezza = Rett.Altezza + 100
Incapsulando questo codice in un metodo e generalizzandolo diamo la possibilit`a di aumentare le dimensioni di qualsiasi rettangolo:
def AumentaRettangolo(Rettangolo, AumentoLargh, AumentoAlt) : Rettangolo.Larghezza = Rettangolo.Larghezza + AumentoLargh; Rettangolo.Altezza = Rettangolo.Altezza + AumentoAlt;
Le variabili AumentoLargh e AumentoAlt indicano di quanto devono essere au- mentate le dimensioni del rettangolo. Invocare questo metodo ha lo stesso effetto di modificare il Rettangolo che `e passato come argomento.
Creiamo un nuovo rettangolo chiamato R1 e passiamolo a AumentaRettangolo:
>>> R1 = Rettangolo() >>> R1.Larghezza = 100.0 >>> R1.Altezza = 200.0 >>> R1.AltoSinistra = Punto() >>> R1.AltoSinistra.x = 0.0; >>> R1.AltoSinistra.y = 0.0; >>> AumentaRettangolo(R1, 50, 100)
Mentre stiamo eseguendo AumentaRettangolo il parametro Rettangolo `e un alias per R1. Ogni cambiamento apportato a Rettangolo modifica direttamente R1 e viceversa.