Fondamenti di Programmazione - cs.unibo.itgaspari/www/teaching/prog12.pdf · – il componente...

29
© Mauro Gaspari - University of Bologna - [email protected] Fondamenti di Programmazione Capitolo 12 Polimorfismo e ereditarietà Prof. Mauro Gaspari: [email protected]

Transcript of Fondamenti di Programmazione - cs.unibo.itgaspari/www/teaching/prog12.pdf · – il componente...

© 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 built­in 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 floating­point 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..