Speaker:
Repository github per questi tutorial:
(Per ripassare link al libro SoftPython )
Esc)import math
class BasicCola:
def __init__(self, sugar, caffeine, water=300):
self.water = water
self.sugar = sugar
self.caffeine = caffeine
def weight(self, years=-1):
return self.water + self.sugar + self.caffeine
class NukaCola(BasicCola):
def __init__(self, cesium):
super().__init__(10, 12)
self.cesium = cesium
def weight(self, years):
return super().weight() + self.cesium_left(years)
def cesium_left(self, years):
return self.cesium * math.pow(0.97716, years)
nuka = NukaCola(7)
print(nuka.weight(10))
327.5559054766441
# To Check, potrebbe essere leggermente diversa
class CoffeeShop:
def __init__(self, name, menu, orders):
self._name = name
self._menu = menu
self._orders = orders
def shop_name(self):
return self._name
def add_order(self, item):
if any(m['item'] == item for m in self._menu):
self._orders += [item]
return 'Order added!'
return 'This item is currently unavailable!'
def fulfill_order(self):
return "The {0} is ready!".format(self._orders.pop(0)) \
if len(self._orders) else 'All orders have been fulfilled!'
def list_orders(self):
return self._orders
def due_amount(self):
items = filter(lambda x: x['item'] in self._orders, self._menu)
return round(sum(item['price'] for item in items)*100)/100
def cheapest_item(self):
return [x['item'] for x in self._menu
if x['price'] == min(*[x['price'] for x in self._menu])].pop()
def drinks_only(self):
return [x['item'] for x in self._menu if x['type'] == 'drink']
def food_only(self):
return [x['item'] for x in self._menu if x['type'] == 'food']
Per prima cosa, recuperiamo l'esempio della serata precedente ma aggiungiamoci alcuni valori
class Animale:
regno_della_vita = "ANIMALE"
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}")
class Animale:
regno_della_vita = "ANIMALE"
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, ambiente):
if ambiente == "terra":
return 10
elif ambiente == "acqua":
print("Non so nuotare")
return 0
else:
print("Non conosco questo ambiente!")
return 0 # Caso limite se ci viene dato un ambiente non corretto
Abbiamo aggiunto una nuova versione, più raffinata del metodo muovi().
Questo prende un parametro e in base a quello ci calcola quanto l'animale si può muovere.
Nel caso non sia capace di muoversi in quel determinato ambiente ci viene comunicato.
Quindi di nuovo potremo creare un AnimaleAcquatico che nuota ma non cammina, un AnimaleVolante che vola ma non nuota etc...
Comodo!
... O forse no?
Abbiamo un modo più ordinato per programmare questa funzionalità:
class MezzoTrasporto:
def __init__(self, nome, terra=0, acqua=0, aria=0):
self.nome = nome
self.terra = terra
self.acqua = acqua
self.aria = aria
def muovi(self, ambiente):
if ambiente == "terra":
return self.__cammina()
elif ambiente == "acqua":
return self.__nuota()
elif ambiente == "aria":
return self.__vola()
else:
print("Non conosco questo ambiente!")
return 0
def __cammina(self):
return self.terra
def __nuota(self):
return self.acqua
def __vola(self):
return self.aria
def __str__(self):
return f"{self.nome}: terra {self.terra}, acqua {self.acqua}, aria {self.aria}"
class Animale:
regno_della_vita = "ANIMALE"
def __init__(self, specie, verso, mezzo_trasporto):
self.specie = specie
self.verso = verso
self.mezzo_trasporto = mezzo_trasporto
def __str__(self):
return f"{self.regno_della_vita}: {self.specie} \n\t mezzo di trasporto({self.mezzo_trasporto})"
def parla(self):
print(f"Il verso di {self.specie} è: {self.verso}")
def muovi(self, ambiente):
print(self.mezzo_trasporto.muovi(ambiente))
pinne = MezzoTrasporto("Pinne", acqua=10)
print(pinne)
print(pinne.muovi("terra"))
print(pinne.muovi("acqua"))
print(pinne.muovi("spazio siderale"))
Pinne: terra 0, acqua 10, aria 0 0 10 Non conosco questo ambiente! 0
pescecane = Animale("Pescecane", "blubvvrr", pinne)
print(pescecane)
pescecane.muovi("acqua")
pescecane.muovi("terra")
pescevolante = Animale("Pescevolante", "blubzooom", MezzoTrasporto("Pinne alari", acqua=12, aria=8))
print(pescevolante)
pescevolante.muovi("acqua")
pescevolante.muovi("aria")
ANIMALE: Pescecane mezzo di trasporto(Pinne: terra 0, acqua 10, aria 0) 10 0 ANIMALE: Pescevolante mezzo di trasporto(Pinne alari: terra 0, acqua 12, aria 8) 12 8
Ma ha senso che i mezzi di trasporto siano pubblici e raggiungibili da chiunque?
class Mobile:
def __init__(self, nome, materiale):
self.nome = nome
self.materiale = materiale
def __str__(self):
return f"Ciao sono uno {self.nome} fatto di {self.materiale}"
# ---------------------------------------------------
sgabello = Mobile("sgabello", "legno di faggio")
sgabello.zampe = MezzoTrasporto("Quattro gambe", terra=10)
print(f"Il mio {sgabello.nome} di {sgabello.materiale} si muove tramite {sgabello.zampe}")
Il mio sgabello di legno di faggio si muove tramite Quattro gambe: terra 10, acqua 0, aria 0
No, non ha molto senso.
Per questo sarebbe bello poter "nascondere" la classe MezzoTrasporto in modo che solamente gli animali possano usarla.
Non vuole dire essere cattivi ma semplicemente cercare di tenere il codice ordinato e separare le responsabilità
--> modularizzazione!
del MezzoTrasporto # <--- Cancello la vecchia classe.
# Mi serve per far funzionare gli esempi successivi
class Animale:
regno_della_vita = "ANIMALE"
def __init__(self, specie, verso, tipo_trasporto, terra=0, acqua=0, aria=0):
self.specie = specie
self.verso = verso
self.mezzo_trasporto = self.MezzoTrasporto(tipo_trasporto, terra, acqua, aria)
def __str__(self):
return f"{self.regno_della_vita}: {self.specie} \n\t mezzo di trasporto({self.mezzo_trasporto})"
def parla(self):
print(f"Il verso di {self.specie} è: {self.verso}")
def muovi(self, ambiente):
print(self.mezzo_trasporto.muovi(ambiente))
# ----- Classe interna!
class MezzoTrasporto:
def __init__(self, nome, terra=0, acqua=0, aria=0):
self.nome = nome
self.terra = terra
self.acqua = acqua
self.aria = aria
def muovi(self, ambiente):
if ambiente == "terra":
return self.__cammina()
elif ambiente == "acqua":
return self.__nuota()
elif ambiente == "aria":
return self.__vola()
else:
print("Non conosco questo ambiente!")
return 0
def __cammina(self):
return self.terra
def __nuota(self):
return self.acqua
def __vola(self):
return self.aria
def __str__(self):
return f"{self.nome}: terra {self.terra}, acqua {self.acqua}, aria {self.aria}"
def muovi(self, ambiente):
if ambiente == "terra":
return self.__cammina()
elif ambiente == "acqua":
return self.__nuota()
elif ambiente == "aria":
return self.__vola()
else:
print("Non conosco questo ambiente!")
return 0
def __cammina(self):
return self.terra
def __nuota(self):
return self.acqua
def __vola(self):
return self.aria
def __str__(self):
return f"{self.nome}: terra {self.terra}, acqua {self.acqua}, aria {self.aria}"
brontosauro = Animale("Dinosauro diplodoco", "moooo", "Quattro zampe enormi", terra=2)
t_rex = Animale("Dinosauro T-Rex", "RAAAAWR!", "Due zampe allenate", terra=15, acqua=2)
print(brontosauro)
brontosauro.muovi("terra")
brontosauro.muovi("aria")
print(t_rex)
t_rex.muovi("terra")
t_rex.muovi("aria")
ANIMALE: Dinosauro diplodoco mezzo di trasporto(Quattro zampe enormi: terra 2, acqua 0, aria 0) 2 0 ANIMALE: Dinosauro T-Rex mezzo di trasporto(Due zampe allenate: terra 15, acqua 2, aria 0) 15 0
marionetta = Mobile("Marionetta", "legno d'abete")
print(f"La mia {marionetta.nome} di {marionetta.materiale}")
La mia Marionetta di legno d'abete
marionetta.zampe = MezzoTrasporto("Due gambette", terra=3)
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-13-7093e6482253> in <module> ----> 1 marionetta.zampe = MezzoTrasporto("Due gambette", terra=3) NameError: name 'MezzoTrasporto' is not defined
Adesso non è più possibile realizzare chimere animal-mobile.
Con buonapace di Geppetto

Un nuovo attrezzo con cui giocare
Come abbiamo visto la volta scorsa, possiamo usare l'ereditarietà per rappresentare la parentela tra specie animali e soprattutto risparmiare codice nella loro implementazione.
Però la volta scorsa siamo partiti dagli animali di terra e abbiamo ereditato al "contrario" rispetto all'evoluzione biologica. Perchè concettualmente a livello informatico vanno bene entrambi i versi.
Tuttavia non è una soluzione pratica.
import abc
from abc import ABC, abstractmethod
class Animale(ABC):
regno_della_vita = "ANIMALE"
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}")
@abstractmethod
def muovi(self):
pass
porygon = Animale("Pokemon Digitale", "bzbzb!")
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-15-f024e2e82f73> in <module> ----> 1 porygon = Animale("Pokemon Digitale", "bzbzb!") TypeError: Can't instantiate abstract class Animale with abstract methods muovi
Porygon è un pokemon digitale, di conseguenza non è una cretura "vera".
Python non sa come istanziarlo!

class AnimaleTerrestre(Animale):
def muovi(self):
print("Cammino!")
lombrico = AnimaleTerrestre("Lombrico", "cripcrip")
print(lombrico)
lombrico.muovi()
ANIMALE: Lombrico Cammino!
Se invece ereditiamo la classe astratta, e reimplementiamo il metodo funziona tutto!
Le classi astratte sono utili per preparare dele funzionalità condivise MA che devono essere personalizzate poi in delle categorie specifiche.
Permettono di creare una struttura ad albero semplice ed elegante
Ma non abusatene, tante volte non ne vale la pena!
Adesso creiamo degli oggetti immutabili... ma perchè?

Oggetti immutabili non possono essere modificati, non essendo modificati saremo sempre sicuri del loro contenuto.
Per esempio in caso di oggetti condivisi, non avere modifiche permette di avere più entità che leggono lo stesso oggetto senza rischi di desincronizzazione.
Pensate a tre cuochi che eseguono una ricetta scritta sullo stesso foglietto. Tecnicamente i tre piatti saranno identici.
Se invece ogni cuoco è libero di cambiare la ricetta i tre risultati finali possono diventare imprevedebili, a seconda del momento in cui ciascun cuoco ha letto ogni parte della ricetta.
class Fossile:
def __init__(self, animale):
self.__animale = animale
def __str__(self):
return str(self.__animale)
def get_specie(self):
return self.__animale.specie
def get_verso(self):
return self.__animale.verso
lombrico_fossile = Fossile(lombrico)
print( lombrico_fossile )
print( lombrico_fossile.get_specie() )
print( lombrico_fossile.get_verso() )
ANIMALE: Lombrico Lombrico cripcrip
Cosa vuol dire?
Statico significa che non serve per forza avere un'istanza di oggetto per funzionare.
import abc
from abc import ABC, abstractmethod
from random import choice
class Animale(ABC):
regno_della_vita = "ANIMALE"
def due_animali_fanno_un(animale1, animale2):
possibilita = ["banco", "gregge", "stormo", "coppia", "alveare"]
print(f"{choice(possibilita)}: {animale1.specie}, {animale2.specie}")
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}")
@abstractmethod
def muovi(self):
pass
# --------------------------
class AnimaleTerrestre(Animale):
def muovi(self):
print("Cammino!")
lucertola = AnimaleTerrestre("Lucertola", "swick")
gallina = AnimaleTerrestre("Gallina", "cocodè")
Animale.due_animali_fanno_un(lucertola, gallina)
lucertola.due_animali_fanno_un(lucertola, gallina)
gregge: Lucertola, Gallina
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-19-fb4d9e10b408> in <module> 4 Animale.due_animali_fanno_un(lucertola, gallina) 5 ----> 6 lucertola.due_animali_fanno_un(lucertola, gallina) TypeError: due_animali_fanno_un() takes 2 positional arguments but 3 were given
Qui come vedete dà errore perchè il metodo non è definito come statico.
E quando si prova a fare la seconda chiamata i parametri non combaciano!
Come si risolve?
Molto facilmente, in questo codice cambia solo 1 riga
import abc
from abc import ABC, abstractmethod
from random import choice
class Animale(ABC):
regno_della_vita = "ANIMALE"
@staticmethod # <------- <------- <------- <------- <------- <------- <------- <------- <-------
def due_animali_fanno_un(animale1, animale2):
possibilita = ["banco", "gregge", "stormo", "coppia", "alveare"]
print(f"{choice(possibilita)}: {animale1.specie}, {animale2.specie}")
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}")
@abstractmethod
def muovi(self):
pass
# --------------------------
class AnimaleTerrestre(Animale):
def muovi(self):
print("Cammino!")
leone = AnimaleTerrestre("Leone", "GROWL!")
elefante = AnimaleTerrestre("Elefante", "baaaaaa")
Animale.due_animali_fanno_un(leone, elefante)
leone.due_animali_fanno_un(leone, elefante)
gregge: Leone, Elefante gregge: Leone, Elefante
Ovvero, come risparmiare ancora più codice e far fare a python il lavoro sporco
Prima di procedere notiamo però una cosa: questo confronto è "sbagliato" perchè nessuno ha definito la funzione per calcolare l'eguaglianza
mucca = AnimaleTerrestre("Bovino", "Mooo!")
mucca_nera = AnimaleTerrestre("Bovino", "Mooo!")
print(mucca == mucca_nera)
False
Proviamo ora a ricreare la stessa struttura degli animali usando la classe astratta E le data classes
from dataclasses import dataclass
import abc
from abc import ABC, abstractmethod
@dataclass
class Animale(ABC):
regno_della_vita = "ANIMALE"
specie: str
verso: str
def parla(self):
print(f"Il verso di {self.specie} è: {self.verso}")
@abstractmethod
def muovi(self):
pass
# ------------------------
class AnimaleTerrestre(Animale):
def muovi(self):
print("Cammino!")
pappagallo = AnimaleTerrestre("Pappagallo", "Arr!")
pappagallo2 = AnimaleTerrestre("Pappagallo", "Arr!")
print(pappagallo)
pappagallo.parla()
print(pappagallo.verso)
print(f"E ora l'uguaglianza... {pappagallo == pappagallo2}!")
AnimaleTerrestre(specie='Pappagallo', verso='Arr!') Il verso di Pappagallo è: Arr! Arr! E ora l'uguaglianza... True!
A questo punto scopriamo una temibile verità
fette_di_torta = 4
amici = 0
print(fette_di_torta / amici)
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) <ipython-input-25-eef80fd69bbf> in <module> 2 amici = 0 3 ----> 4 print(fette_di_torta / amici) ZeroDivisionError: division by zero
Fino a che non troveremo degli amici avremo questo errore...
ma già che ci siamo... che cos'è quell'errore?
... Beh! Ma è una classe naturalmente!

Quindi possiamo creare le nostre eccezioni!
class AnimaleMitologicoException(Exception):
def __init__(self, specie):
super().__init__()
self.specie = specie
def __str__(self):
stringa = f" Hai selezionato una specie mitologica. Non puoi creare questo animale! {self.specie}"
return super().__str__() + stringa
import abc
from abc import ABC, abstractmethod
from random import choice
class Animale(ABC):
regno_della_vita = "ANIMALE"
__animali_mitologici = ["Drago", "Chimera", "Basilisco"]
def __init__(self, specie, verso):
if specie not in self.__animali_mitologici:
self.specie = specie
self.verso = verso
else:
raise AnimaleMitologicoException(specie)
def __str__(self):
return f"{self.regno_della_vita}: {self.specie}"
def parla(self):
print(f"Il verso di {self.specie} è: {self.verso}")
@abstractmethod
def muovi(self):
pass
# --------------------------
class AnimaleTerrestre(Animale):
def muovi(self):
print("Cammino!")
dragone_cinese = AnimaleTerrestre("Dragone cinese", "Nihao")
print(dragone_cinese)
drago = AnimaleTerrestre("Drago", "Fuoco!")
ANIMALE: Dragone cinese
--------------------------------------------------------------------------- AnimaleMitologicoException Traceback (most recent call last) <ipython-input-28-d9ff4b154660> in <module> 3 print(dragone_cinese) 4 ----> 5 drago = AnimaleTerrestre("Drago", "Fuoco!") <ipython-input-27-f22d800c539a> in __init__(self, specie, verso) 13 self.verso = verso 14 else: ---> 15 raise AnimaleMitologicoException(specie) 16 17 def __str__(self): AnimaleMitologicoException: Hai selezionato una specie mitologica. Non puoi creare questo animale! Drago
Cosa sono i file CSV?
Riferimenti: SoftPython: File a linea SoftPython: File CSV
File esempio-1.csv che trovi nella stessa cartella di questo foglio Jupyter:
animale,anni
cane,12
gatto,14
pellicano,30
scoiattolo,6
aquila,25
animale, anni, : cane, 12Proviamo ad importare questo file:
import csv
with open('esempio-1.csv', encoding='utf8', newline='') as f:
lettore = csv.reader(f, delimiter=',')
for riga in lettore:
print('Abbiamo appena letto una riga!')
print(riga)
print('')
Abbiamo appena letto una riga! ['animale', 'anni'] Abbiamo appena letto una riga! ['cane', '12'] Abbiamo appena letto una riga! ['gatto', '14'] Abbiamo appena letto una riga! ['pellicano', '30'] Abbiamo appena letto una riga! ['scoiattolo', '6'] Abbiamo appena letto una riga! ['aquila', '25']
import csv
with open('esempio-1.csv', encoding='utf8', newline='') as f:
lettore = csv.reader(f, delimiter=',')
for riga in lettore:
print('Abbiamo appena letto una riga!')
print(riga)
print('')
Il with definisce un blocco con all'interno le istruzioni:
as f: crea variabile f (puoi inventare qualunque nome)lettore è un oggetto cosiddetto 'iterabile'
Encoding (codifica)
utf8, latin1, ....)import csv
with open('esempio-1.csv', encoding='utf-8', newline='') as f:
lettore = csv.DictReader(f, delimiter=',')
for diz in lettore:
print(diz)
{'animale': 'cane', 'anni': '12'}
{'animale': 'gatto', 'anni': '14'}
{'animale': 'pellicano', 'anni': '30'}
{'animale': 'scoiattolo', 'anni': '6'}
{'animale': 'aquila', 'anni': '25'}
dictOrderedDictdictimport csv
# Per scrivere, RICORDATI di specificare l'opzione 'w'
# ATTENZIONE: 'w' rimpiazza *completamente* eventuali file esistenti!
with open('file-scritto.csv', 'w', encoding='utf8', newline='') as csv_da_scrivere:
scrittore = csv.writer(csv_da_scrivere, delimiter=',')
scrittore.writerow(['Questo', 'è', 'uno header'])
scrittore.writerow(['dati', 'di', 'esempio'])
scrittore.writerow(['altri', 'dati', 'di esempio'])
Puoi creare un CSV instanziando un oggetto writer:
Per scrivere un nuovo CSV prendendo dati da un CSV esistente:
with per la lettura dentro uno per la scrittura:import csv
# Per scrivere, RICORDATI di specificare l'opzione 'w'
# ATTENZIONE: 'w' rimpiazza *completamente* eventuali file esistenti!
# ATTENZIONE: l'handle *esterno* l'abbiamo chiamato csv_da_scrivere
with open('esempio-1-arricchito.csv', 'w', encoding='utf-8', newline='') as csv_da_scrivere:
scrittore = csv.writer(csv_da_scrivere, delimiter=',')
# Nota come questo 'with' sia dentro quello esterno
# ATTENZIONE: l'handle *interno* l'abbiamo chiamato csv_da_leggere
with open('esempio-1.csv', encoding='utf-8', newline='') as csv_da_leggere:
lettore = csv.reader(csv_da_leggere, delimiter=',')
for riga in lettore:
riga.append("qualcos'altro")
scrittore.writerow(riga)
scrittore.writerow(riga)
scrittore.writerow(riga)
Per evitare problemi:
esempio-1-arricchito.csv') che per gli handle (es: csv_da_scrivere)Vediamo se il file è stato effettivamente scritto provando a leggerlo:
with open('esempio-1-arricchito.csv', encoding='utf-8', newline='') as csv_da_leggere:
lettore = csv.reader(csv_da_leggere, delimiter=',')
for riga in lettore:
print(riga)
['animale', 'anni', "qualcos'altro"] ['animale', 'anni', "qualcos'altro"] ['animale', 'anni', "qualcos'altro"] ['cane', '12', "qualcos'altro"] ['cane', '12', "qualcos'altro"] ['cane', '12', "qualcos'altro"] ['gatto', '14', "qualcos'altro"] ['gatto', '14', "qualcos'altro"] ['gatto', '14', "qualcos'altro"] ['pellicano', '30', "qualcos'altro"] ['pellicano', '30', "qualcos'altro"] ['pellicano', '30', "qualcos'altro"] ['scoiattolo', '6', "qualcos'altro"] ['scoiattolo', '6', "qualcos'altro"] ['scoiattolo', '6', "qualcos'altro"] ['aquila', '25', "qualcos'altro"] ['aquila', '25', "qualcos'altro"] ['aquila', '25', "qualcos'altro"]
Link alla challenge su Softpython
Per questa challenge vi viene richiesto di scrivere uno script che:
Facile no?
PS per leggere, usa l’encoding 'latin-1', altrimenti il file potrebbe non aprirsi proprio o potresti vedere strani
Fatto questo vi chiediamo di mettere nuovamente mano allo script aggiungendo delle classi che vi permettano di modellare i personaggi.
Probabilmente avrete fatto caso al fatto che il dataset è "sporco" ovvero pieno di:
Noi che siamo persone precise non vogliamo un tale disordine e per questo vi chiediamo di:
E se proprio non ne aveste avuto abbastanza potete lanciarvi anche nella terza (Ed ultima per ora!) parte dell'esercizio:
La pulizia dell'anno di nascita