Python Base¶

Python Biella Group¶


Quarta serata, 24/10/2022¶

Speaker:

  • James Luca Bosotti (Assistente e Studente @ Università di Trento)
  • bosottiluca@gmail.com

Repository github per questi tutorial:

  • https://github.com/PythonBiellaGroup/PythonBase/tree/main/teoria

(Per ripassare link al libro SoftPython )

  • (Per anteprima slide premi Esc)

Soluzione esercizi terza serata¶

Nuka Cola¶

In [1]:
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

Caffetteria¶

In [2]:
# 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!'
In [ ]:
   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']

Class composition¶

Per prima cosa, recuperiamo l'esempio della serata precedente ma aggiungiamoci alcuni valori

In [3]:
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}")           
In [4]:
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à:

In [5]:
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}"
In [6]:
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))
In [7]:
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
In [8]:
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

Classi interne¶

Ma ha senso che i mezzi di trasporto siano pubblici e raggiungibili da chiunque?

In [9]:
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!

In [10]:
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}"
In [ ]:
        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}"
In [11]:
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
In [12]:
marionetta = Mobile("Marionetta", "legno d'abete")
print(f"La mia {marionetta.nome} di {marionetta.materiale}")
La mia Marionetta di legno d'abete
In [13]:
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

Classi Astratte¶

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.

In [14]:
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
In [15]:
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!

In [16]:
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!

Classi immutabili¶

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.

In [17]:
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

Classi con metodi statici¶

Cosa vuol dire?

Statico significa che non serve per forza avere un'istanza di oggetto per funzionare.

In [18]:
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!")
In [19]:
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

In [20]:
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!")
In [21]:
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

Data Classes¶

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

In [22]:
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

In [23]:
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!")    
In [24]:
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!

Classi di errore¶

A questo punto scopriamo una temibile verità

In [25]:
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!

In [26]:
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
In [27]:
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!")
In [28]:
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

Leggere file, esempio CSV¶

Cosa sono i file CSV?

  • file di testo
  • ‘Comma separated Value’
  • li leggeremo con sistemi base

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
  • la prima linea sono i nomi delle colonne, separati da virgole (comma in inglese): animale, anni
  • I campi nelle righe successive sono pure separati da virgole , : cane, 12
  • non ci sono spazi dopo le virgole

Proviamo ad importare questo file:

In [32]:
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:

  • dice a Python che, in ogni caso, anche se accadono errori, dopo aver usato il file Python deve chiudere automaticamente il file
  • alla fine della riga as f: crea variabile f (puoi inventare qualunque nome)

lettore è un oggetto cosiddetto 'iterabile'

  • se usato in un for produce una sequenza di righe dal csv

Encoding (codifica)

  • dipende dal sistema operativo ed editor con cui è stato scritto il file
  • Python non può divinare la codifica (es utf8, latin1, ....)
  • senza specificarla, il programma può comportarsi in modo diverso a seconda del sistema operativo / lingua locale

Leggere come dizionari¶

In [33]:
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'}
  • l'oggetto csv.DictReader recupera le linee come dizionari
  • le chiavi sono i nomi dei campi presi dall'intestazione
  • NOTA: diverse versioni di Python producono diversi dizionari:
    • $<$ 3.6: dict
    • 3.6, 3.7: OrderedDict
    • $\geq$ 3.8: dict

Scrivere un CSV¶

In [34]:
import 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:

**ATTENZIONE: ASSICURATI DI SCRIVERE NEL FILE GIUSTO!** Se non stai più che attento ai nomi dei file, **rischi di cancellare dati** !!!

Leggere e scrivere un CSV¶

Per scrivere un nuovo CSV prendendo dati da un CSV esistente:

  • annidare un with per la lettura dentro uno per la scrittura:
In [35]:
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)    
**ATTENZIONE A SCAMBIARE I NOMI DEI FILE!** Quando leggiamo e scriviamo è facile commettere un errore e sovrascrivere accidentalmente i nostri preziosi dati.

Per evitare problemi:

  • usa nomi espliciti sia per i file di output (es: esempio-1-arricchito.csv') che per gli handle (es: csv_da_scrivere)
  • fai una copia di backup dei dati da leggere
  • controlla sempre prima di eseguire il codice !

Vediamo se il file è stato effettivamente scritto provando a leggerlo:

In [36]:
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"]

Sfida!¶

Anche quest'incontro è terminato, e come di rito ci dobbiamo lasciare con una sfida.

Per questo motivo vi invitiamo a visitare virtualmente la storia del Trentino-Alto-Adige, analizzando il dataset dei...

Personaggi storici trentini!¶

Link alla challenge su Softpython

Link al dataset

Per questa challenge vi viene richiesto di scrivere uno script che:

  1. Legga dal file ogni riga
  2. Salvi in una struttura dati adeguata solamente il NOME, LUOGO di nascita e DATA di nascita di ciascun personaggio
  3. Salvi in un nuovo file questi dati

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:

  • Dati mancanti messi come "sconosciuto"
  • Luoghi di nascita scritti per esteso, come sigla di provincia, come città etc.
  • Date non coerenti. Alcune precise per anno mese giorno, altre solamente l'anno altre solamente il secolo

Noi che siamo persone precise non vogliamo un tale disordine e per questo vi chiediamo di:

  1. Convertire 'sconosciuto' in '' (stringa vuota)
  2. Convertire le sigle di città in nomi estesi. A tal fine, usate il dizionario province definito più sotto
  3. Se un nome o sigla di città NON è tra parentesi, mettete il risultato tra parentesi, togliendo la virgola

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

Link alla terza parte

Grazie mille!¶

James Luca Bosotti

bosottiluca@gmail.com