Python fin dalla nascita è stato un linguaggio orientato agli oggetti. Motivo per cui, creare e usare le classi in python è facile proprio come usare tutti gli altri costrutti del linguaggio. Con questo articolo vedremo come farti diventare un esperto nell’usare il paradigma ad oggetti di Python.

Se non si ha alcuna precedente esperienza con la programmazione Object-oriented (OO), sarebbe magari meglio trovare una guida che meglio spieghi i concetti basilari, per quanto semplice cercheremo di rendere questa guida molte nozioni saranno date per scontato.

Terminologia della programmazione Object-Oriented

Classe: Un prototipo di oggetto definito dall’utente che definisce un insieme di attributi che caratterizzano ogni oggetto della classe. Attributi intesi come variabili o metodi (funzioni).

Variabile di classe: Una variabile condivisa da tutte le istanze della classe, l’equivalente di un attributo static in java. Le variabili di classe in generale sono definite all’interno di una classe ma fuori da qualsiasi metodo. Le variabili di classe hanno un uso meno frequente rispetto alle variabili d’istanza.

Variabile d’istanza: Una variabile che contiene dati associati all’oggetto della classe istanziata.

Overloading di funzione: L’assegnamento di uno o più comportamenti ad un metodo/funzione in base al numero di parametri passati.

Eredità: Il trasferimento di caratteristiche della classe ad altre classi che son derivate da essa.

Istanza: Un oggetto individuale di una certa classe. Un oggetto “Cane” che appartiene alla classe Animale, per esempio, è un’istanza della classe Animale.

Istanziazione: Il processo di creazione di un’istanza di una classe.

Metodo: Un tipo speciale di funzione definita all’interno di una classe.

Oggetto: Un’unica istanza di una struttura dati che è definita dalla sua classe. Un oggetto comprende sia le variabili d’istanza, di classe e i metodi.

Creare le Classi

Specificata la terminologia di base, andiamo a vedere com’è possibile creare delle classi in Python:

class NomeClasse:
   'Stringa opzionale per la documentazione della classe'
   ...

La keyword riservata class in python serve proprio a definire una classe. Il nome della classe segue immediatamente la keyword class, così come visto nell’esempio sopra. La classe può contenere una documentazione, che meglio la descrive, e che è la stringa definita subito dopo la dichiarazione della classe.
Quella stringa può essere richiamata attravverso: NomeClasse.__doc__.

Esempio

Quello che segue è un esempio di una classe Python:

class Impiegato:
   'Classe base per tutti gli impiegati'
   impCount = 0
 
   def __init__(self, nome, salario):
      self.nome = nome
      self.salario = salario
      Impiegato.impCount += 1
 
   def displayCount(self):
     print "Totale Impiegati %d" % Impiegato.impCount
 
   def displayImpiegato(self):
      print "Nome: ", self.nome,  ", Salario: ", self.salario

La variabile ImpCount è una variabile di classe condivisa fra tutte le istanze della classe. Può essere richiamata direttamente dall’interno o dall’esterno della classe con Impiegato.impCount. Il primo metodo __init__() è un metodo speciale, chiamato metodo costruttore o metodo d’inizializzazione, che Python richiama quando voi creare una nuova istanza della classe.

Altri metodi della classe possono essere dichiarati come fossero normali funzioni, con l’operatore def, con l’unica eccezione che riguarda il primo parametro, che dev’essere necessariamente self. Questo parametro è automaticamente gestito e passato da python quando richiamate il metodo, non dovete preoccuparvi di passarci qualcosa.

Nota Bene. Idealmente, dovreste definire le vostre classi in file separati, dunque importarle nel vostro programma principale usando l’istruzione import.

Istanziare degli oggetti

Per creare delle istanze della classe, chiamate la classe usando il nome della classe e passandoci un numero di argomenti pari al numero di argomenti che __init__() accetta.

"Questo crea il primo oggetto della classe Impiegato"
imp1 = Impiegato("Rossi", 2000)
"Questo crea il secondo oggetto della classe Impiegato"
imp2 = Impiegato("Musolino", 5000)

Accedere agli attributi

E’ possibile accedere agli attributi degli oggetti attraverso la dot notation. In questo modo:

imp1.displayImpiegato()
imp2.displayImpiegato()
print "Impiegati totali %d" % Impiegato.impCount

Ora mettiamo assieme tutti i concetti visti fin’ora:

#!/usr/bin/python
 
class Impiegato:
   'Classe base comune per tutti gli Impiegati'
   impCount = 0
 
   def __init__(self, nome, salario):
      self.nome = nome
      self.salario = salario
      Impiegato.impCount += 1
 
   def displayCount(self):
     print "Totale Impiegati %d" % Impiegato.impCount
 
   def displayImpiegato(self):
      print "Nome : ", self.nome,  ", Salario: ", self.salario
 
"Crea il primo oggetto della classe Impiegato"
imp1 = Impiegato("Rossi", 2000)
"Crea il secondo oggetto della classe Impiegato"
imp2 = Impiegato("Musolino", 5000)
imp1.displayImpiegato()
imp2.displayImpiegato()
print "Totale Impiegati %d" % Impiegato.impCount

Il risultato prodotto sarà questo:


Nome : Zara, Salario: 2000
Nome : Manni, Salario: 5000
Totale Impiegati 2

E’ possibile aggiungere, rimuovere o modificare attributi ad oggetti in qualsiasi momento:

imp1.eta = 10  # aggiunge un attributo 'eta'.
imp1.eta = 6 # modifica l'attributo 'eta'.
del imp1.eta  # cancella l'attributo 'eta'.
</pre
 
Invece di usare la dot notation per accedere agli attributi, si possono usare anche le seguenti funzioni:
 
<code>getattr(obj, name[, default])</code>: Per accedere all'attributo di un oggetto.
 
<code>hasattr(obj,name)</code>: Per controllare se un attributo esiste o meno.
 
<code>setattr(obj,name,value)</code>: Per settare un attributo. Se l'attributo non esiste verrà creato.
 
<code>delattr(obj, name)</code>: Per cancellare un attributo.
 
Esempio:
 
<pre lang="python">
hasattr(imp1, 'eta')    # Restituisce true se l'attributo 'eta' esiste.
getattr(imp1, 'eta')    # Restituisce il valore dell'attributo 'eta'.
setattr(imp1, 'eta', 8) # Setta l'attributo 'eta' a 8.
delattr(imp1, 'eta')    # Cancella l'attributo 'eta'

Attributi predefiniti in Python

Ogni classe Python possiede degli attributi predefiniti che possono essere richiamati usando la dot notation come qualsiasi altro attributo. Vediamoli assieme:

__dict__: Dizionario contenente il namespace della classe, cioè un elenco degli attributi e dei metodi che possiede la classe.

__doc__: La stringa di documentazione della classe, se definita, altrimenti non contiene nulla.

__name__: Nome della classe.

__module__: Nome del modulo in cui è definita la classe, questo attributo è uguale a “__main__” nella modalità interattiva da console.

__bases__: Una tupla (anche vuota) contenente le classi base, in ordine di occorrenza nella base class list.

Ecco un esempio d’utilizzo degli attributi predefiniti in Python:

#!/usr/bin/python
 
class Impiegato:
   'Classe base comune a tutti gli Impiegati'
   impCount = 0
 
   def __init__(self, nome, salario):
      self.nome = nome
      self.salario = salario
      Impiegato.impCount += 1
 
   def displayCount(self):
     print "Totale Impiegato %d" % Impiegato.impCount
 
   def displayImpiegato(self):
      print "nome : ", self.nome,  ", salario: ", self.salario
 
print "Impiegato.__doc__:", Impiegato.__doc__
print "Impiegato.__nome__:", Impiegato.__nome__
print "Impiegato.__module__:", Impiegato.__module__
print "Impiegato.__bases__:", Impiegato.__bases__
print "Impiegato.__dict__:", Impiegato.__dict__

Distruggere gli oggetti (Garbage Collection)

Python distrugge gli oggetti non necessari (tipi predefiniti o istanze di classe) automaticamente per liberare memoria.
Il processo secondo cui Python periodicamente richiama blocchi di memoria che non sono più in uso è chiamato garbage collection.

Il Garbage Collector di Python gira durante la normale esecuzione del programma ed è interpellato quando il reference count di un oggetto raggiunge 0.
Il reference count di un oggetto cambia quando il numero di alias che puntano all’oggetto cambiano. Esso aumenta quando per esempio l’oggetto è piazzato in una lista, una tupla o un dizionario. Diminuisce quando è espressamente cancellato con del, quando è riassegnato o quando la sua referenza è fuori dallo scope. Quando il reference count di un oggetto raggiunge zero, Python lo “colleziona” automaticamente.

a = 40      # Crea l'oggetto <40>
b = a       # Aumenta ref. count  di <40> 
c = [b]     # Aumenta ref. count  di <40> 
 
del a       # Decrease ref. count  di <40>
b = 100     # Decrease ref. count  di <40> 
c[0] = -1   # Decrease ref. count  di <40>

Normalmente non noterete quando il garbage collectore distrugge un’istanza orfana e ne reclama lo spazio. Tuttavia una classe può implementare un metodo speciale chiamato __del__(), chiamato distruttore, che viene invocato quando un’istanza sta per essere distrutta.

Classi ed Ereditarietà

Piuttosto che riscrivere una classe, è possibile creare una classe derivandola da una preesistente. Per definire la classe genitore da cui ereditare metodi e attributi, è sufficiente specificarne il nome nelle parentesi.

E’ possibile usare gli attributi ereditati (dalla classe padre) come se nativamente appartenessero alla classe figlio.

SINTASSI:

class SubClassName (ParentClass1[, ParentClass2, ...]):
   'Stringa opzionale di documentazione della classe'
   ...

ESEMPIO:

class Parent:        # define parent class
   parentAttr = 100
   def __init__(self):
      print "Calling parent constructor"
 
   def parentMethod(self):
      print 'Calling parent method'
 
   def setAttr(self, attr):
      Parent.parentAttr = attr
 
   def getAttr(self):
      print "Parent attribute :", Parent.parentAttr
 
class Child(Parent): # define child class
   def __init__(self):
      print "Calling child constructor"
 
   def childMethod(self):
      print 'Calling child method'
 
c = Child()          # instance of child
c.childMethod()      # child calls its method
c.parentMethod()     # calls parent's method
c.setAttr(200)       # again call parent's method
c.getAttr()          # again call parent's method

che produce i risultati seguenti:

Calling child constructor
Calling child method
Calling parent method
Parent attribute : 200

In modo simile è possibile ereditare più classi genitori:

class A:        # definisce la classe A
.....
 
class B:         # definisce la classe B
.....
 
class C(A, B):   # sottoclasse di A e B
.....

E’ possibile usare alcune funzioni per verificare le relazioni fra classi e istanze:

issubclass(sub, sup): E’ una funzione che restituisce true se la sottoclasse sub è di fatti una sottoclasse della superclasse sup.

isinstance(obj, Class): Restituisce true se l’oggetto obj è un’istanza della classe Class o se è un’istanza di una sottoclasse di Class.

Sovrascrivere i metodi

E’ sempre possibile sovrascrivere i metodi della classe genitore. Una ragione per farlo è perchè magari si desidera una funzionalità differente di quel metodo nella sottoclasse.

ESEMPIO:

class Parent:        # Definiamo la classe Parent
   def myMethod(self):
      print 'Chiamato il metodo genitore'
 
class Child(Parent): # Definiamo la classe Child
   def myMethod(self):
      print 'Chiamato il metodo figlio'
 
c = Child()          # istanza di child
c.myMethod()         # child chiama il metodo sovrascritto

Darà come output:

Chiamato il metodo figlio

Sovrascrivere i metodi di base

Ecco alcuni metodi di base che è possibile sovrascrivere in Python:

metodi di base - python

Overloading degli operatori

Supponiamo abbiate creato una classe Vettore per rappresentare vettori bi-dimensionali, cosa succede quando provate a sommarli? il più delle volte Python vi urlerà contro.

Motivo per cui è utile effettuare l’overloading del metodo __add__ nella vostra classe Vettore, per definire il comportamento di una somma fra oggetti di classi definite da noi:

class Vector:
   def __init__(self, a, b):
      self.a = a
      self.b = b
 
   def __str__(self):
      return 'Vettore(%d, %d)' % (self.a, self.b)
 
   def __add__(self,other):
      return Vector(self.a + other.a, self.b + other.b)
 
v1 = Vector(2,10)
v2 = Vector(5,-2)
print v1 + v2

Che produrrà il seguente risultato:

Vettore(7, 8)

Nascondere i dati

Gli attributi di un oggetto possono essere visibili o meno all’infuori della classe. In questi casi, si può nominare l’attributo con un prefisso “doppio undercore” __, e questi attributi non saranno direttamente visibili fuori dalla classe.

ESEMPIO:

class JustCounter:
   __secretCount = 0
 
   def count(self):
      self.__secretCount += 1
      print self.__secretCount
 
counter = JustCounter()
counter.count()
counter.count()
print counter.__secretCount

Che produrrà questo risultato:

1
2
Traceback (most recent call last):
  File "test.py", line 12, in <module>
    print counter.__secretCount
AttributeError: JustCounter instance has no attribute '__secretCount'

Python protegge internamente questi attributi cambiando il modo in cui è possibile referenziarli. Per accedervi infatti è necessario usare una notazione del tipo: oggetto._NomeClasse__nomeAttributo.

Per far funzionare l’esempio sopra infatti dovremmo sostituire l’ultima linea con:

print counter._JustCounter__secretCount

Che dunque produrrà il risultato aspettato:

1
2
2

La guida è terminata. Per qualsiasi dubbio invitiamo a commentare sotto e se vi è piaciuta condividere! Presto ce ne saranno delle altre.