© Mauro Gaspari University of Bologna [email protected]
Fondamenti di Programmazione
Capitolo 12Polimorfismo e ereditarietà
Prof. Mauro Gaspari: [email protected]
© Mauro Gaspari University of Bologna [email protected]
Una classe tempoclass Time:
def __init__(self,hours=0,minutes=0,seconds=0):
self.hours = hours
self.minutes = minutes
self.seconds = seconds
def __str__(self):
temp = str(self.hours)+':'+str(self.minutes)
return = temp+':'+str(self.seconds)
>>> time = Time(11,59,30)
>>> print time
11:59:30
© Mauro Gaspari University of Bologna [email protected]
La funzione addTime● La funzione addTime somma due tempi: si tratta
di una funzione “pura” perché non modifica i suoi argomenti.
def addTime(t1, t2): sum = Time() sum.hours = t1.hours + t2.hours sum.minutes = t1.minutes + t2.minutes sum.seconds = t1.seconds + t2.seconds return sum
© Mauro Gaspari University of Bologna [email protected]
Esempio: uso di addTime
>>> currentTime = Time(9,14,30)>>> breadTime = Time(3,35,0)>>> doneTime = addTime(currentTime, breadTime)>>> print doneTime
Domanda: La funzione addTime così definita,funziona sempre bene?
© Mauro Gaspari University of Bologna [email protected]
AddTime aggiornatadef addTime(t1, t2):
sum = Time()
sum.hours = t1.hours + t2.hours
sum.minutes = t1.minutes + t2.minutes
sum.seconds = t1.seconds + t2.seconds
if sum.seconds >= 60:
sum.seconds = sum.seconds - 60
sum.minutes = sum.minutes + 1
if sum.minutes >= 60:
sum.minutes = sum.minutes - 60
sum.hours = sum.hours + 1
return sum
© Mauro Gaspari University of Bologna [email protected]
Modificatori● La funzione addTime è corretta ma ha il difetto di essere un po'
lunga.
● Nel realizzare una funzione a volte può essere opportuno modificare uno o più degli oggetti utilizzati al fine di semplificarne il codice e renderla più efficiente.
● Queste funzioni tipicamente modificano gli argomenti che gli vengono passati e li restituiscono modificati.
● Le chiamiamo modificatori.
© Mauro Gaspari University of Bologna [email protected]
Esempio di modificatore
def increment(time, seconds): time.seconds = time.seconds + seconds
if time.seconds >= 60: time.seconds = time.seconds - 60 time.minutes = time.minutes + 1
if time.minutes >= 60: time.minutes = time.minutes - 60 time.hours = time.hours + 1
Funziona sempre bene questa funzione?
© Mauro Gaspari University of Bologna [email protected]
Incremento di un numero qualsiasi di secondi
def increment(time, seconds): time.seconds = time.seconds + seconds
while time.seconds >= 60: time.seconds = time.seconds - 60 time.minutes = time.minutes + 1
while time.minutes >= 60: time.minutes = time.minutes - 60 time.hours = time.hours + 1
Si può fare di meglio?
© Mauro Gaspari University of Bologna [email protected]
Osservazioni● Quale dei due approcci è migliore?
● Tutto quello che in genere si può fare con una funzione pura si può anche fare
con un modificatore.
● In genere i programmi basati su funzioni pure si realizzano più velocemente e
sono meno soggetti ad errori.
● Mentre i programmi realizzati come modificatori a volte risultano essere più
efficienti (usano meno memoria).
● Si consiglia di scrivere programmi funzionali quando è possibile e utilizzare i
modificatori solo quando è particolarmente conveniente.
© Mauro Gaspari University of Bologna [email protected]
Sviluppo di prototipi● Si scrive una prima versione del programma: prototipo che
potrebbe non funzionare in alcuni casi.
● Si testa e si correggono eventuali errori.
● Questo approccio è effettivo e può essere utilizzato nella pratica, ma:
– il codice può risultare complicato perché deve trattare molti casi speciali (potrebbe non essere strettamente necessario).
– potrebbe inoltre essere inaffidabile, perché è impossibile controllare in modo esaustivo tutti i casi.
© Mauro Gaspari University of Bologna [email protected]
Un alternativa: pianificare lo sviluppo● Si studia prima il problema con cura e poi si
fornisce una soluzione.● Ad esempio studiando il problema precedente si
può osservare che:– un oggetto tempo è dato da tre numeri in base 60.– il componente secondi è la colonna dell'uno, i minuti
sono quella 60 e le ore la colonna del 3600.● addTime è quindi una somma in base 60.
© Mauro Gaspari University of Bologna [email protected]
Approccio alternativo per addTime● Convertire il tempo in un numero e sfruttare la conoscenza sulla
sua struttura per realizzare le operazioni.
● Questo numero rappresenta i secondi.
def convertToSeconds(t): minutes = t.hours * 60 + t.minutes seconds = minutes * 60 + t.seconds return seconds
© Mauro Gaspari University of Bologna [email protected]
Viceversa
def makeTime(seconds): time = Time() time.hours = seconds/3600 seconds = seconds - time.hours * 3600 time.minutes = seconds/60 seconds = seconds - time.minutes * 60 time.seconds = seconds return time
© Mauro Gaspari University of Bologna [email protected]
Nuova versione di addTime
def addTime(t1, t2):
seconds = convertToSeconds(t1) + convertToSeconds(t2)
return makeTime(seconds)
© Mauro Gaspari University of Bologna [email protected]
Osservazioni● In un certo modo convertire da base 60 a base 10 e poi tornare
indietro è più astratto e complicato che lavorare direttamente sul tempo che invece è intuitivo.
● L'idea ha però permesso di semplificare le operazioni rendendo il programma più corto e leggibile, con meno possibili errori.
● In questo modo sarà inoltre più facile aggiungere ulteriori caratteristiche in seguito.
● Quello che si vede è che se si rende un problema più difficile (o più generale) spesso si semplificano le cose, perché ci sono meno casi speciali e quindi meno opportunità di errore.
© Mauro Gaspari University of Bologna [email protected]
Algoritmi● Quando si scrive una soluzione generale che funziona per una
certa classe di problemi, si dice che si è scritto un algoritmo.
● Cosa è un algoritmo:
– moltiplicazione con tabellina? >NO– moltiplicazione ripetendo la somma? >SI
● Un algoritmi è un processo meccanico, una serie di passi ben definiti che permettono di risolvere un certo problema.
© Mauro Gaspari University of Bologna [email protected]
Overloading di Operatori● In alcuni linguaggi è possibile cambiare la definizione di alcuni
operatori builtin quando sono applicati a tipi definiti dall'utente.
● Questa caratteristica si definisce overloading di operatori (= operator overloading).
● Python supporta questa caratteristica. Ad esempio per modificare l'operatore somma (“+”) viene fornito il metodo denominato __add__.
© Mauro Gaspari University of Bologna [email protected]
Esempio: somma di punti.
class Point:
# previously defined methods here...
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
NB. per sommare due punti si crea un nuovo punto che contiene la somma e si restituisce direttamente.
© Mauro Gaspari University of Bologna [email protected]
Osservazioni
● Il primo parametro è self come accade sempre con i metodi.
● self corrisponde al primo parametro dell'operazione di somma e viene utilizzato per capire che metodo applicare, se è di tipo Point si applica il metodo __add__ definito nella classe Point se esiste.
>>> p1 = Point(3, 4)
>>> p2 = Point(5, 7)
>>> p3 = p1 + p2
>>> print p3
(8, 11)
© Mauro Gaspari University of Bologna [email protected]
Ancora su overloading● P1 + P2 è equivalente a p1.__add__(p2)
● ovvero applica il metodo __add__ all'oggetto p1.
● la notazione infissa è sicuramente più comoda.
● Ci sono diversi modi per fare override della moltiplicazione:
– definire un metodo __mul__;– definire un metodo __rmul__;– definirli entrambi.
© Mauro Gaspari University of Bologna [email protected]
Overloading per *
def __mul__(self, other):
return self.x * other.x + self.y * other.y
def __rmul__(self, other):
return Point(other * self.x, other * self.y)
● La prima definizione è analoga alla somma.● La seconda si applica quando l'operatore a sinistra è
di tipo primitivo e quello a destra è un punto.● NB. ad other corrisponde un tipo che non può essere
moltiplicato con un floatingpoint il metodo da errore
© Mauro Gaspari University of Bologna [email protected]
Esempio
>>> p1 = Point(3, 4)
>>> p2 = Point(5, 7)
>>> print p1 * p2
43
>>> print 2 * p2
(10, 14)
>>> print p2 * 2
AttributeError: 'int' object has no attribute 'x'
NB. il messaggio di errore non è molto illuminante!
© Mauro Gaspari University of Bologna [email protected]
Polimorfismo● la maggior parte dei metodi che abbiamo visto
lavorano solo su un tipo specifico. In genere quando si scrive un oggetto si scrivono metodi che operano su quell'oggetto.
● Ci sono però alcuni casi in cui la stessa operazione può essere applicata ad oggetti di tipo diverso come la somma che abbiamo visto.
© Mauro Gaspari University of Bologna [email protected]
Esempio
>>> def multadd (x, y, z):... return x * y + z
>>> multadd (3, 2, 1)7>>> p1 = Point(3, 4)>>> p2 = Point(5, 7)>>> print multadd (2, p1, p2)(11, 15)>>> print multadd (p1, p2, 1)44
© Mauro Gaspari University of Bologna [email protected]
Funzioni polimorfe
● Una funzione che accetta parametri di tipo diverso si dice polimorfa (= polymorphic).
def frontAndBack(front): import copy back = copy.copy(front) back.reverse() print str(front) + str(back)
>>> myList = [1, 2, 3, 4]>>> frontAndBack(myList)[1, 2, 3, 4][4, 3, 2, 1]
© Mauro Gaspari University of Bologna [email protected]
Funzioni polimorfe● Come si può capire se una certa funzione è applicabile ad un certo
tipo?
● Ad esempio, come si fa a capire se la funzione appena definita si applica su un Point?
● Regola di polimorfismo:
Se tutte le operazioni che appaiono dentro una funzione possono essere applicate ad oggetti di un certo tipo, allora quella funzione
è applicabile ad oggetti di quel tipo.
© Mauro Gaspari University of Bologna [email protected]
Quindi?● Per poter utilizzare la funzione frontAndBack è necessario che il
tipo supporti le funzioni: copy, reverse e print.
– copy funziona su tutti gli oggetti.– print funziona bene perché abbiamo già definito il metodo
__str__.
● Quindi bisogna definire il metodo reverse nella classe point.
© Mauro Gaspari University of Bologna [email protected]
Esempio
def reverse(self): self.x , self.y = self.y, self.x
>>> p = Point(3, 4)>>> frontAndBack(p)(3, 4)(4, 3)
© Mauro Gaspari University of Bologna [email protected]
Composizione di oggetti● È possibile comporre gli oggetti con altri tipi:
– creare liste di oggetti;– creare oggetti che contengono liste;– creare oggetti che contengono oggetti;– dizionari di oggetti;– etc..