Speaker:
Repository github per questi tutorial:
(Per ripassare link al libro SoftPython )
Esc)Cambia il focus:
Il codice non è più una linea più o meno dritta in cui dobbiamo esplicitare tutte le istruzioni
Ma il compito del programmatore diventa quello di descrivere un ecosistema di oggetti e relazioni
Il modo in cui queste relazioni si intersecano definirà il comportamento degli oggetti
# NO OOP
animali = []
animali.append( ("Gatto", "miao") )
animali.append( ("Cane", "bau") )
animali.append( ("Coccodrillo", "??") )
def parla(animale):
print(animale[1])
for animale in animali:
parla(animale)
miao bau ??
# OOP!
class Animale:
def __init__(self, specie, verso):
self.specie = specie
self.verso = verso
def parla(self):
print(f"Il verso di {self.specie} è: {self.verso}")
animali = []
animali.append(Animale("Gatto", "miao"))
animali.append(Animale("Cane", "bau"))
animali.append(Animale("Coccodrillo", "??"))
for animale in animali:
animale.parla()
Il verso di Gatto è: miao Il verso di Cane è: bau Il verso di Coccodrillo è: ??
Beh ma quindi dove sta il vantaggio?
Il vantaggio sta nella creazione di uno "standard"
Il codice ora sa cos'è un Animale, sa che ha due variabili e un metodo
Sa che non ha senso esista un animale senza specie o senza verso
Sa che tutti gli animali hanno un modo per parlare
class Animale:
pass
mio_gatto = Animale()
Animale è una classe, è una rappresentazione di tutti i possibili animali che possono esistere dal mio gatto ai temibili Vermi delle sabbie di Dune.
I metodi sono le "funzioni" degli oggetti. Descrivono i suoi comportamenti
class Animale:
def parla(self):
print(f"Il verso di {self.specie} è: {self.verso}")
Ok, ma cos'è "self"?

Se Animale è una classe, al contrario mio_gatto, Verme delle sabbie o ogni altro animale reale (Cioè che esiste e ti più mordere) è un'istanza della classe Animale.
self rappresenta quindi l'istanza stessa.
quando si legge print(self.verso) si deve intendere "stampa il verso proprio della mia istanza"
Aggiungiamo pezzi alla nostra classe Animale
class Animale:
def __init__(self, specie, verso): # <--
self.specie = specie # <--
self.verso = verso # <--
def parla(self):
print(f"Il verso di {self.specie} è: {self.verso}")
Prima avevamo spiegato a Python che i nostri animali potevano parlare tramite la variabile verso. Ma non avevamo mica detto che valore avesse o come si dovesse assegnare.
__init__ risolve questo problema. E' un metodo che si chiama costruttore e spiega come vada creata un istanza della nostra classe.
Notate come il suo nome inizia e finisca con due underscores. Questo è un segnale per Python che si tratta di un metodo speciale
self.specie = specie
Questa linea sta dicendo di assegnare il parametro del metodo alla variabile d'istanza specie.
Sembra rindondante per i nomi usati, ma è in realtà una buona pratica per non perdere di vista cosa vada dove
Una variabile d'istanza è una proprietà che ogni rappresentante di quella classe possiede ma che è diversa tra ciascun individuo.
Una variabile di classe invece è una proprietà che tutti gli appartenendi a quella classe possiedono nella stessa e ugual misura.
Per esempio
Istanza: Peso, altezza, propensione al rischio, numero di scarpe
Classe: classificazione regno animale,
class Animale:
regno_della_vita = "ANIMALE" # <--
fotosintesi = False # <--
def __init__(self, specie, verso):
self.specie = specie
self.verso = verso
def parla(self):
print(f"Il verso di {self.specie} è: {self.verso}")
regno_della_vita è una variabile di classe, non è necessario inizializzarla
self.specie è una variabile di istanza, assume un valore potenzialmente diverso per ogni animale
zanzara = Animale("zanzara", "bzzz")
elefante_confuso = Animale("elefante", "bzzz")
print(zanzara.regno_della_vita)
print(elefante_confuso.regno_della_vita)
zanzara.parla()
elefante_confuso.parla()
ANIMALE ANIMALE Il verso di zanzara è: bzzz Il verso di elefante è: bzzz
pinguino = Animale("Pinguino", "skee")
batterio = Animale("Batterio", "...")
batterio.fotosintesi = True
batterio.regno_della_vita += "...Forse"
print("Batterio")
print(batterio.fotosintesi)
print(batterio.regno_della_vita)
Batterio True ANIMALE...Forse
print("Pinguino")
print(pinguino.fotosintesi)
print(pinguino.regno_della_vita)
Pinguino False ANIMALE
Animale.fotosintesi = True
delfino = Animale("Delfino", "kekeke")
print("Delfino")
print(delfino.fotosintesi)
print(delfino.regno_della_vita)
Delfino True ANIMALE
E' possibile modificare una variabile di classe MA la modifica sarà valida solo per l'istanza per cui è stata modificata
class Animale:
regno_della_vita = "ANIMALE"
fotosintesi = False
def __init__(self, specie, verso):
self.specie = specie
self.verso = verso
def parla(self):
print(f"Il verso di {self.specie} è: {self.verso}")
def parla(self, ritmo): # <---
print(f"Il verso di {self.specie} è: {(self.verso+' ') * ritmo}") # <---
cucu = Animale("Cucù", "cucù!")
cucu.parla(5)
cucu.parla()
Il verso di Cucù è: cucù! cucù! cucù! cucù! cucù!
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-11-f46403af23a4> in <module> 17 18 cucu.parla(5) ---> 19 cucu.parla() TypeError: parla() missing 1 required positional argument: 'ritmo'
Ovvero, definire più volte un metodo usando firme diverse e scegliere a runtime quello corretto da utilizzare.
In caso di "tentato overloading" infatti, solo l'ultima funzione definita avrà effetto.
Diverso è il caso dell'overriding.
print(cucu)
print(batterio)
<__main__.Animale object at 0x7f57802cfd30> <__main__.Animale object at 0x7f57802912b0>
Qui vedete come Python stampi una cosa strana alla print dell'animale.
Si tratta della print di default di tutti gli oggetti e indica la cella di memoria in cui è contenuta l'istanza dell'oggetto.
Non sempre è molto informativa e quindi sarebbe interessante cambiarla.
class Animale:
regno_della_vita = "ANIMALE"
fotosintesi = False
def __init__(self, specie, verso):
self.specie = specie
self.verso = verso
def __str__(self): # <---
return f"{self.regno_della_vita}: {self.specie}" # <---
def parla(self):
print(f"Il verso di {self.specie} è: {self.verso}")
piccione = Animale("Piccione Comune", "grugrugru")
print(piccione)
ANIMALE: Piccione Comune
Per modificarla è sufficiente ridefinire il metodo __str__
In questo modo cambia il modo con l'oggetto verrà trasformato in stringa in ogni contesto in cui questo verrà richiesto.
Ma perchè proprio __str__?
Perchè è uno dei cosiddetti Special Methods (Docs Ufficiale) che una volta sovrascritti cambiano le funzionalità degli oggetti.
Altri metodi speciali sono:
Una delle feature fondamentali dell'OOP permette di usare il pensiero di analogia
"Questa cosa funziona come quella ma..."
class Animale:
regno_della_vita = "ANIMALE"
fotosintesi = False
def __init__(self, specie, verso):
self.specie = specie
self.verso = verso
def __str__(self):
return f"{self.regno_della_vita}: {self.specie}"
def parla(self):
print(f"Il verso di {self.specie} è: {self.verso}")
def muovi(self): # <---
print("Questo animale si muove su terra") # <---
Aggiungiamo il metodo per muoversi
Ora aggiungiamo una nuova classe di animali
class Animale_Anfibio(Animale):
def nuota(self):
print("Questo animale nuota")
polpo = Animale_Anfibio("Polpo", "blub")
polpo.nuota()
polpo.muovi()
Questo animale nuota Questo animale si muove su terra
Quindi vedete che il nostro polpo ha un mezzo in più
class Animale_Acquatico(Animale_Anfibio):
def muovi(self):
print("Questo animale NON può andare sulla terraferma")
self.nuota()
pesce_chirurgo = Animale_Acquatico("Pesce Chirurgo", "blub")
pesce_chirurgo.muovi()
Questo animale NON può andare sulla terraferma Questo animale nuota
Qui invece vedete come è possibile usare l'ereditarietà anche per "togliere" delle funzionalità.
Semplicemente facendo un override di un dato metodo impediamo il suo utilizzo.
Come fare per avere delle variabili private?
class Animale_Anfibio(Animale):
def __init__(self, specie, verso, pinne):
super().__init__(specie, verso)
self.__pinne = pinne
def nuota(self):
print("Questo animale nuota")
def get_pinne(self):
print(self.__pinne)
salmone = Animale_Anfibio("Salmone", "sushi", 5)
salmone.get_pinne()
5
print(salmone.__pinne)
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-77-157e0ebf4cdb> in <module> ----> 1 print(salmone.__pinne) AttributeError: 'Animale_Anfibio' object has no attribute '__pinne'
Come vedete è proprio impossibile accedere dall'esterno al valore di pinne
Per scrivere i getter e i setter ci sono vari modi, questo che segue usa i decoratori standard
Ovvero @property e @name.setter
class Portal:
def __init__(self):
self.__name =''
# Using @property decorator
@property
# Getter method
def name(self):
print("Hai usato il getter!")
return self.__name
# Setter method
@name.setter
def name(self, val):
print("Hai usato il setter!")
self.__name = val
p = Portal();
p.name = 'GeeksforGeeks'
print (p.name)
Hai usato il setter! Hai usato il getter! GeeksforGeeks
Passiamo ora alle sfide!
Siete assunti per scrivere il software che gestisce il mixing e le ricette della famosa e mirabolante NukaCola Corporation.
Dovete scrivere il software che rappresenta ogni ricetta a partire dalla BasicCola.
La BasicCola è, appunto, la base di ogni altra ricetta. Traccia l'ammontare di acqua, zucchero e caffeina. L'acqua normalmente è sempre 300g (per le dosi da laboratorio). La ricetta deve essere in grado di stampare il proprio peso totale con un metodo appropriato.
La ricetta pià famosa ovviamente è quella della NukaCola, derivata dalla base grazie all'aggiunta dell'ingredienti segreto: il cesio. E' un additivo dal sapore energico vagamente radioattivo ma la gente ne va matta e quindi... \spallucce :D
Aggiungete al software un pezzo che permetta di modellare anche la ricetta della NukaCola che quindi tenga traccia del cesio.
Per calcolare il peso totale della NukaCola però bisogna tenere a mente il decadimento radioattivo del cesio, perchè una bottiglia creata oggi o una creata 4 anni fa hanno un peso ben diverso. Per calcolare il peso occorre fare cesio * 0.97716^anni
Scrivi una classe Caffetteria che abbia 3 variabili:
Ogni MenuItem è composto da
Aggiungi alla Caffetteria questi sette metodi:
Photo by Andre Mouton from Pexels: https://www.pexels.com/photo/closeup-photo-of-primate-1207875/
Photo by Torsten Dettlaff: https://www.pexels.com/photo/lighting-strike-67102/