|
Impariamo adesso a creare nuove classi. Nel caso più semplice
possiamo dichiarare una classe vuota:
>>> class Void:
... pass
...
>>> x = Void()
>>> x
<__main__.Void instance at 85ff80>
>>> x.a
Traceback (innermost last)
AttributeError: a
>>> x.a=1
>>> y = Void()
>>> y.a=2
>>> print x.a,y.a
1 2
Utilizzando la keyword class abbiamo dichiarato una nuova classe,
in questo chiamata Void. In una definizione di classe possono
esserci comandi di qualsiasi genere, che vengono eseguiti durante
la definizione della classe. I comandi per noi interessanti
comunque sono quelli che dichiarano attributi, ovvero variabili e
funzioni che diventano campi e metodi della classe. Gli altri comandi
possono servire per esempio a definire dei metodi condizionalmente
(per esempio in un modo sotto Windows e un altro sotto Unix) ma è
meglio non abusare di queste possibilità.
Ad una classe non vengono assegnati una volta e per tutte tutti i
suoi attributi, ma possono essere aggiunti anche dopo che l'istanza
è stata creata. Infatti in genere gli attributi vengono aggiunti
in fase di inizializzazione. Come si vede nel'esempio, dopo aver
dichiarato la classe, utilizziamo la funzione costruttore, avente lo
stesso nome della classe, per creare nuove istanze. Nell'esempio
utilizzando Void come funzione abbiamo creato l'istanza. Ogni
instanza è un namespace separato che inizialmente non contiene
alcun attributo. La natura dinamica del Python ci consente di
aggiungere, "al volo", nuovi attribuiti, nell'esempio a. Che le
istanze siano namespace separati e indipendenti si vede anche dal
fatto che creando due istanze, possiamo assegnare ad ogni istanza un
valore diverso per l'attribuito a. Otteniamo, come ci si
aspetta, due a separati e indipendenti per ogni istanza.
class List:
def __init__(self, data, next=None):
self.data=data
self.next=next
def append(self, data):
curr = self
while not curr.next is None:
curr = curr.next
curr.next = List(data)
return self
def push(self, data):
return List(data,self)
def printall(self):
while not self is None:
print self.data
self=self.next
Listato 1
Le classi divengono interessanti quando contengono
campi e metodi. Nel Listato 1 possiamo vedere la
dichiarazione di una classe List, contenuta nel file
list.py, che andiamo subito ad utilizzare:
>>> import list
>>> x = list.List(2)
>>> x.printall
<method List.printall of List instance at 8654a0>
>>> x.printall()
2
>>> x = x.push(1)
>>> x.printall()
1
2
>>> x=x.append(3)
>>> x.printall()
1
2
3
Come si vede, abbiamo definito una classe lista con tre metodi:
append, push e printall. Esaminiamo ora in
dettaglio il meccanismo della definizione della classe. Dichiarando
una classe ci ritroviamo con una funzione costruttore che ha lo stesso
nome della classe. In realtà questo costruttore crea soltanto
l'oggetto istanza, ma non lo inizializza. Se occorre effettuare delle
inizializzazioni supplementari (caso abbastanza frequente), si deve
definire una funzione di inizializzazione che si deve chiamare
__init__. L'uso dei doppi underscore all'inizio e alla fine
è una convenzione Python usata ovunque sia necessario definire
nomi che hanno un significato speciale, e non è limitata solo a
questo caso. La __init__ viene chiamata automaticamente dopo
che è stata creata l'istanza dell'oggetto.
Veniamo ora a quello che è un punto critico, ed è
basilare per comprendere tutto il meccanismo della OOP in
Python. Dovrebbe essere chiaro come funzionano in Python le funzioni
(non c'è niente di speciale, a parte il fatto che sono
solitamente contenute dentro dei moduli). Ora, nelle classi non ci
sono funzioni ma metodi. In OOP, in generale i metodi sono
funzioni speciali, in quanto conoscono l'oggetto a cui
appartengono. In Java, C++ e altri linguaggi OOP l'accesso all'oggetto
corrente è implicito e nascosto: il programmatore non se ne
accorge. In Python invece l'oggetto corrente viene passato
esplicitamente come primo parametro della chiamata del
metodo. Confrontando l'ultimo esempio con il listato il meccanismo
dovrebbe diventare abbastanza chiaro. Precisamente, scrivendo x =
list.List(2) si costruisce un oggetto istanza, poi viene chiamato
automaticamente __init__(self, data, next=None). Il primo
parametro di __init__ è l'oggetto appena costruito, il
secondo è il parametro fornito al costruttore (2). In questo
esempio si utilizza anche la feature dei parametri di default:
poiché non abbiamo specificato il terzo parametro questo assume
il valore di default None. Il meccanismo è analogo quando
invochiamo i metodi: chiamato x.printall() viene chiamata la
funzione printall passando x come primo parametro. Per
convenzione il primo parametro di un metodo viene chiamato
self. Non c'è niente di speciale in questo nome ma non
seguire questa convenzione può compromettere la leggibilità
del vostro listato ad altri programmatori Python. Non ci sono modi per
accedere direttamente ai campi di un oggetto: occorre usare sempre il
prefisso self. Per quanto questa cosa possa apparire noiosa, in
pratica questo migliora la leggibilità e evita ambiguità con
le variabili locali.
|