Formati dati

Introduzione

In questo tutorial parleremo di formati dei dati:

  • file testuali

    • File a linee

    • CSV

    • breve panoramica sui cataloghi open data

    • menzione licenze (Creative Commons CC-Zero)

    • menzione JSON

    • menzione XML

  • menzione file binari

    • immagini

    • fogli Excel

Che fare

  • scompatta lo zip in una cartella, dovresti ottenere qualcosa del genere:

formats
    formats.ipynb
    formats-sol.ipynb
    jupman.py

ATTENZIONE: Per essere visualizzato correttamente, il file del notebook DEVE essere nella cartella szippata.

  • apri il Jupyter Notebook da quella cartella. Due cose dovrebbero aprirsi, prima una console e poi un browser. Il browser dovrebbe mostrare una lista di file: naviga la lista e apri il notebook formats.ipynb

  • Prosegui leggendo il file degli esercizi, ogni tanto al suo interno troverai delle scritte ESERCIZIO, che ti chiederanno di scrivere dei comandi Python nelle celle successive.

Scorciatoie da tastiera:

  • Per eseguire il codice Python dentro una cella di Jupyter, premi Control+Invio

  • Per eseguire il codice Python dentro una cella di Jupyter E selezionare la cella seguente, premi Shift+Invio

  • Per eseguire il codice Python dentro una cella di Jupyter E creare una nuova cella subito dopo, premi Alt+Invio

  • Se per caso il Notebook sembra inchiodato, prova a selezionare Kernel -> Restart

1. File a linee

I file a linee tipicamente sono file di testo che contengono informazioni raggruppate per linee. Un esempio usando personaggi storici potrebbe essere un file così:

Leonardo
da Vinci
Sandro
Botticelli
Niccolò
Macchiavelli

Si nota subito una regolarità: le prime due linee contengono i dati di Leonardo da Vinci, prima il nome e poi il cognome. Le successive due linee hanno invece i dati di Sandro Botticelli, di nuovo prima il nome e poi il cognome, e così via

Un programma che potremo voler fare potrebbe essere leggere le linee e stampare a video nomi e cognomi così:

Leonardo da Vinci
Sandro Botticelli
Niccolò Macchiavelli

Per iniziare ad avere un’approssimazione del risultato finale, possiamo aprire il file, leggere solo la prima linea e stamparla:

[1]:
with open('people-simple.txt', encoding='utf-8') as f:
    linea=f.readline()
    print(linea)

Leonardo

Che è successo? Esaminiamo le varie linee:

il comando open

Il comando

open('people-simple.txt', encoding='utf-8')

ci permette di aprire il file di testo dicendo a Python il percorso del file 'people-simple.txt' e la codifica con cui è stato scritto (encoding='utf-8').

La codifica

La codifica dipende dal sistema operativo e dell’editor con cui è stato scritto il file. Quando apriamo un file, Python non può divinare la codifica, e se non gliela specifichiamo potrebbe aprirlo assumendo una codifica diversa dall’originale - tradotto, affidandoci al caso o sbagliando codifica in seguito potremmo vedere dei caratteri strani (tipo quadratini invece di lettere accentate).

In genere, quando apri un file, prova prima a specificare la codifica utf-8 che è la più comune scrivendo encoding='utf-8', e se per caso non va bene prova invece encoding='latin-1' (solitamente utile se il file è stato scritto su sistemi Windows). Se apri file scritti in posti più esotici, tipo in Cina, potresti dover usare un’altro encoding. Per approfondire queste questioni, quando hai tempo leggi Immersione in Python - Cap 4 - Stringhe e Immersione in Python - Cap 11 - File, entrambe letture caldamente consigliate.

il with

Il with definisce un blocco con all’interno le istruzioni:

with open('people-simple.txt', encoding='utf-8') as f:
    linea=f.readline()
    print(linea)

Abbiamo usato il with per dire a Python che in ogni caso, anche se accadono errori, vogliamo che dopo aver usato il file, e cioè eseguito le istruzioni nel blocco interno (il linea=f.readline() e print(linea)) Python deve chiudere automaticamente il file. Chiudere propriamente un file evita di sprecare risorse di memoria e creare errori paranormali. Se vuoi evitare di andare a caccia di file zombie mai chiusi, ricordati sempre di aprire i file nei blocchi with! Inoltre, alla fine della riga nella parte as f: abbiamo assegnato il file ad una variabile chiamata qui f, ma potevamo usare un qualunque altro nome.

ATTENZIONE: Per indentare il codice, usa SEMPRE sequenze di 4 spazi bianchi. Sequenze di 2 soli spazi per quanto consentite non sono raccomandate.

ATTENZIONE: A seconda dell’editor che usi, premendo TAB potresti ottenere una sequenza di spazi bianchi come accade in Jupyter (4 spazi che sono raccomandati), oppure un carattere speciale di tabulazione (da evitare)! Per quanto noiosa questa distinzione ti possa apparire, ricordatela perchè potrebbe generare errori molto difficili da scoprire.

ATTENZIONE: Nei comandi che creano blocchi come il with, ricordati di mettere sempre il carattere dei doppi punti : alla fine della linea !

Il comando

linea=f.readline()

mette nella variabile linea l’intera linea, come una stringa. Attenzione: la stringa conterrà alla fine anche il carattere speciale di ritorno a capo !

Ti chiederai da dove venga fuori quel readline. Come quasi tutto in Python, la nostra variabile f che rappresenta il file appena aperto è un oggetto, e ogni oggetto, a seconda del suo tipo, ha dei metodi particolari che possiamo usare su di esso. In questo caso il metodo è readline. Clicca qua per maggiori informazioni sugli oggetti.

Il comando seguente stampa il contenuto della stringa:

print(linea)

✪ 1.1 ESERCIZIO: Prova a riscrivere nella cella qua il blocco with appena visto, ed esegui la cella premendo Control-Invio. Riscrivi il codice con le dita, non con il copia e incolla ! Fai attenzione ad indentare correttamente con gli spazi il blocco.

Mostra soluzione
[2]:
# scrivi qui


Leonardo

✪ 1.2 ESERCIZIO: immagino ti starai chiedendo che cosa è esattamente quella f, e cosa faccia esattamente il metodo readlines. Quando ti trovi in queste situazioni, puoi aiutarti con le funzioni type e help. Questa volta, copia e incolla direttamente sempre lo stesso codice qua sotto, ma aggiungi a mano dentro il blocco with i comandi:

  • print(type(f))

  • print(help(f))

  • print(help(f.readline))      # Attenzione: ricordati il 'f.' prima del readline !!

Ogni volta che aggiungi qualcosa, prova ad eseguire con Control+Invio e vedere cosa succede.

Mostra soluzione
[3]:
# scrivi qui il codice (copia e incolla)


<class '_io.TextIOWrapper'>
Help on built-in function readline:

readline(size=-1, /) method of _io.TextIOWrapper instance
    Read until newline or EOF.

    Returns an empty string if EOF is hit immediately.

None
Help on TextIOWrapper object:

class TextIOWrapper(_TextIOBase)
 |  Character and line based layer over a BufferedIOBase object, buffer.
 |
 |  encoding gives the name of the encoding that the stream will be
 |  decoded or encoded with. It defaults to locale.getpreferredencoding(False).
 |
 |  errors determines the strictness of encoding and decoding (see
 |  help(codecs.Codec) or the documentation for codecs.register) and
 |  defaults to "strict".
 |
 |  newline controls how line endings are handled. It can be None, '',
 |  '\n', '\r', and '\r\n'.  It works as follows:
 |
 |  * On input, if newline is None, universal newlines mode is
 |    enabled. Lines in the input can end in '\n', '\r', or '\r\n', and
 |    these are translated into '\n' before being returned to the
 |    caller. If it is '', universal newline mode is enabled, but line
 |    endings are returned to the caller untranslated. If it has any of
 |    the other legal values, input lines are only terminated by the given
 |    string, and the line ending is returned to the caller untranslated.
 |
 |  * On output, if newline is None, any '\n' characters written are
 |    translated to the system default line separator, os.linesep. If
 |    newline is '' or '\n', no translation takes place. If newline is any
 |    of the other legal values, any '\n' characters written are translated
 |    to the given string.
 |
 |  If line_buffering is True, a call to flush is implied when a call to
 |  write contains a newline character.
 |
 |  Method resolution order:
 |      TextIOWrapper
 |      _TextIOBase
 |      _IOBase
 |      builtins.object
 |
 |  Methods defined here:
 |
 |  __getstate__(...)
 |
 |  __init__(self, /, *args, **kwargs)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |
 |  __new__(*args, **kwargs) from builtins.type
 |      Create and return a new object.  See help(type) for accurate signature.
 |
 |  __next__(self, /)
 |      Implement next(self).
 |
 |  __repr__(self, /)
 |      Return repr(self).
 |
 |  close(self, /)
 |      Flush and close the IO object.
 |
 |      This method has no effect if the file is already closed.
 |
 |  detach(self, /)
 |      Separate the underlying buffer from the TextIOBase and return it.
 |
 |      After the underlying buffer has been detached, the TextIO is in an
 |      unusable state.
 |
 |  fileno(self, /)
 |      Returns underlying file descriptor if one exists.
 |
 |      OSError is raised if the IO object does not use a file descriptor.
 |
 |  flush(self, /)
 |      Flush write buffers, if applicable.
 |
 |      This is not implemented for read-only and non-blocking streams.
 |
 |  isatty(self, /)
 |      Return whether this is an 'interactive' stream.
 |
 |      Return False if it can't be determined.
 |
 |  read(self, size=-1, /)
 |      Read at most n characters from stream.
 |
 |      Read from underlying buffer until we have n characters or we hit EOF.
 |      If n is negative or omitted, read until EOF.
 |
 |  readable(self, /)
 |      Return whether object was opened for reading.
 |
 |      If False, read() will raise OSError.
 |
 |  readline(self, size=-1, /)
 |      Read until newline or EOF.
 |
 |      Returns an empty string if EOF is hit immediately.
 |
 |  seek(self, cookie, whence=0, /)
 |      Change stream position.
 |
 |      Change the stream position to the given byte offset. The offset is
 |      interpreted relative to the position indicated by whence.  Values
 |      for whence are:
 |
 |      * 0 -- start of stream (the default); offset should be zero or positive
 |      * 1 -- current stream position; offset may be negative
 |      * 2 -- end of stream; offset is usually negative
 |
 |      Return the new absolute position.
 |
 |  seekable(self, /)
 |      Return whether object supports random access.
 |
 |      If False, seek(), tell() and truncate() will raise OSError.
 |      This method may need to do a test seek().
 |
 |  tell(self, /)
 |      Return current stream position.
 |
 |  truncate(self, pos=None, /)
 |      Truncate file to size bytes.
 |
 |      File pointer is left unchanged.  Size defaults to the current IO
 |      position as reported by tell().  Returns the new size.
 |
 |  writable(self, /)
 |      Return whether object was opened for writing.
 |
 |      If False, write() will raise OSError.
 |
 |  write(self, text, /)
 |      Write string to stream.
 |      Returns the number of characters written (which is always equal to
 |      the length of the string).
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |
 |  buffer
 |
 |  closed
 |
 |  encoding
 |      Encoding of the text stream.
 |
 |      Subclasses should override.
 |
 |  errors
 |      The error setting of the decoder or encoder.
 |
 |      Subclasses should override.
 |
 |  line_buffering
 |
 |  name
 |
 |  newlines
 |      Line endings translated so far.
 |
 |      Only line endings translated during reading are considered.
 |
 |      Subclasses should override.
 |
 |  ----------------------------------------------------------------------
 |  Methods inherited from _IOBase:
 |
 |  __del__(...)
 |
 |  __enter__(...)
 |
 |  __exit__(...)
 |
 |  __iter__(self, /)
 |      Implement iter(self).
 |
 |  readlines(self, hint=-1, /)
 |      Return a list of lines from the stream.
 |
 |      hint can be specified to control the number of lines read: no more
 |      lines will be read if the total size (in bytes/characters) of all
 |      lines so far exceeds hint.
 |
 |  writelines(self, lines, /)
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors inherited from _IOBase:
 |
 |  __dict__

None
Leonardo

Prima abbiamo messo il contenuto della prima linea nella variabile line, ora potremmo metterlo in una variabile dal nome più significativo, come nome. Non solo, possiamo anche direttamente leggere la linea successiva nella variabile cognome e poi stampare la concatenazione delle due:

[4]:
with open('people-simple.txt', encoding='utf-8') as f:
    nome=f.readline()
    cognome=f.readline()
    print(nome + ' ' + cognome)

Leonardo
 da Vinci

PROBLEMA ! La stampa mette comunque uno strano ritorno a capo. Come mai? Se ti ricordi, prima ho detto che readline legge il contenuto della linea in una stringa aggiungendo alla fine anche il carattere speciale di ritorno a capo. Per eliminarlo, puoi usare il comando rstrip():

[5]:
with open('people-simple.txt', encoding='utf-8') as f:
    nome=f.readline().rstrip()
    cognome=f.readline().rstrip()
    print(nome + ' ' + cognome)

Leonardo da Vinci

✪ 1.3 ESERCIZIO: Di nuovo, riscrivi il blocco qua sopra nella cella sotto, ed esegui la cella con Control+Invio. Domanda: che succede se usi strip() invece di rstrip()? Ed lstrip()? Riesci a dedurre il significato di r e l ? Se non ci riesci, prova ad usare il comando python help chiamando help(string.rstrip)

Mostra soluzione
[6]:
# scrivi qui


Leonardo da Vinci

Benissimo, abbiamo la prima linea ! Adesso possiamo leggere tutte le linee in sequenza. A tal fine possiamo usare un ciclo while:

[7]:
with open('people-simple.txt', encoding='utf-8') as f:
    linea=f.readline()
    while linea != "":
        nome = linea.rstrip()
        cognome=f.readline().rstrip()
        print(nome + ' ' + cognome)
        linea=f.readline()
Leonardo da Vinci
Sandro Botticelli
Niccolò Macchiavelli

NOTA In Python ci sono metodi più concisi per leggere un file tdi testo linea per linea, ma abbiamo usato questo approccio per esplicitare tutti i passaggi

Cosa abbiamo fatto? Per prima cosa, abbiamo aggiunto un ciclo while in un nuovo blocco

Attenzione: nel nuovo blocco while, dato che è già all’interno del blocco esterno with, le istruzioni sono indentate di 8 spazi e non più 4! Se per caso sbagli gli spazi, possono succedere brutti guai!

Prima leggiamo una linea, e due casi sono possibili:

  1. siamo alla fine del file (o il file è vuoto): in questo caso la chiamata a readline() ritorna una stringa vuota

  2. non siamo alla fine del file: la prima linea è messa come una stringa dentro la variabile linea.

  3. we are not at the end of the file: the first line is put as a string inside the variable line. Dato che Python internamente usa un puntatore per tenere traccia della posizione a cui si è durante la lettura del file, dopo una lettura questo puntatore è mosso all’inizio della riga successiva. In questo modo una chiamata successiva a readline() leggera la linea dalla nuova posizione.

Nel blocco while diciamo a Python di continuare il ciclo fintanto che linea non è vuota. In questo caso, dentro il blocco while estraiamo il nome dalla linea e lo mettiamo nella variabile nome (rimuovendo il carattere extra di ritorno a capo con la rstrip() come fatto in precedenza), e poi procediamo leggendo la nuova riga ed estraendo il risultato dentro la variabile cognome. Infine, leggiamo di nuovo la linea dentro la variabile linea così sarà pronta per la prossima iterazione di estrazione nome. Se la linea è vuota il ciclo terminerà:

while linea != "":                   # entra il ciclo se la linea contiene caratteri
    nome = linea.rstrip()            # estrae il nome
    cognome=f.readline().rstrip()    # legge la nuova linea ed estrae il cognome
    print(nome + ' ' + cognome)
    linea=f.readline()               # legge la prossima linea

✪ 1.4 ESERCIZIO: Di nuovo come prima, riscrivi nella cella qua sotto il codice col while appena spiegato, facendo MOLTA attenzione all’indentazione (per la linea del with esterno fai pure copia e incolla):

Mostra soluzione
[8]:
# scrivi qui il codice col while interno


Leonardo da Vinci
Sandro Botticelli
Niccolò Macchiavelli

File a linee people-complex:

Guarda il file people-complex.txt, più complesso:

nome: Leonardo
cognome: da Vinci
data di nascita: 1452-04-15
nome: Sandro
cognome: Botticelli
data di nascita: 1445-03-01
nome: Niccolò
cognome: Macchiavelli
data di nascita: 1469-05-03

Supponendo di leggere il file per voler stampare questo output, come faresti ?

Leonardo da Vinci, 1452-04-15
Sandro Botticelli, 1445-03-01
Niccolò Macchiavelli, 1469-05-03

Suggerimento 1: Per ottenere dalla stringa 'abcde' la sottostringa 'cde', che inizia all’indice 2, puoi usare l’operatore di parentesi quadre, indicando l’indice di inizio seguito dai doppi punti :

[9]:
x = 'abcde'
x[2:]
[9]:
'cde'
[10]:
x[3:]
[10]:
'de'

Suggerimento 2: Per sapere la lunghezza di una stringa, usa la funzione len:

[11]:
len('abcde')
[11]:
5

✪ 1.5 ESERCIZIO: Scrivi qua sotto la soluzione dell’esercizio ‘People complex’:

Mostra soluzione
[12]:
# scrivi qui


Leonardo da Vinci, 1452-04-15
Sandro Botticelli, 1445-03-01
Niccolò Macchiavelli, 1469-05-03

Esercizio file a linee immersione-in-python-toc

Questo esercizio è più difficile, se sei alle prime armi puoi saltarlo e passare ai CSV.

Il libro Immersione in Python in Italiano è bello e ti fornisce un PDF, che però ha un problema: se provi a stamparlo scoprirai che manca l’indice. Senza perderci d’animo, abbiamo trovato un programmino che ci ha estratto i titoli in un file come segue, che però come scoprirai non è esattamente bello da vedere. Dato che siamo Python ninja, abbiamo deciso di trasformare i titoli grezzi in una tabella dei contenuti vera e propria. Sicuramente ci sono metodi più furbi per compiere questa operazione, come caricare il pdf in Python con una apposita libreria per i pdf, ma pareva un esercizio interessante.

Ti viene consegnato il file immersione-in-python-toc.txt:

BookmarkBegin
BookmarkTitle: Il vostro primo programma Python
BookmarkLevel: 1
BookmarkPageNumber: 38
BookmarkBegin
BookmarkTitle: Immersione!
BookmarkLevel: 2
BookmarkPageNumber: 38
BookmarkBegin
BookmarkTitle: Dichiarare funzioni
BookmarkLevel: 2
BookmarkPageNumber: 41
BookmarkBeginint
BookmarkTitle: Argomenti opzionali e con nome
BookmarkLevel: 3
BookmarkPageNumber: 42
BookmarkBegin
BookmarkTitle: Scrivere codice leggibile
BookmarkLevel: 2
BookmarkPageNumber: 44
BookmarkBegin
BookmarkTitle: Stringhe di documentazione
BookmarkLevel: 3
BookmarkPageNumber: 44
BookmarkBegin
BookmarkTitle: Il percorso di ricerca di import
BookmarkLevel: 2
BookmarkPageNumber: 46
BookmarkBegin
BookmarkTitle: Ogni cosa &#232; un oggetto
BookmarkLevel: 2
BookmarkPageNumber: 47

Scrivi un programma python per stampare e video il seguente output:

Il vostro primo programma Python  38
   Immersione!  38
   Dichiarare funzioni  41
      Argomenti opzionali e con nome  42
   Scrivere codice leggibile  44
      Stringhe di documentazione  44
   Il percorso di ricerca di import  46
   Ogni cosa è un oggetto  47

Per questo esercizio, dovrai inserire nell’output degli spazi artificiali, in una quantità determinata dalle righe BookmarkLevel.

DOMANDA: che cos’è lo strano valore &#232; alla fine del file originale ? Dobbiamo riportarlo nell’output ?

SUGGERIMENTO 1: Per convertire una stringa in numero intero, usa la funzione int:

[13]:
x = '5'
[14]:
x
[14]:
'5'
[15]:
int(x)
[15]:
5

Attenzione: int(x) ritorna un valore, e non modifica mai l’argomento x !

SUGGERIMENTO 2: Per sostituire un sottostringa in una stringa, si può usare la funzione replace:

[16]:
x = 'abcde'
x.replace('cd', 'HELLO' )
[16]:
'abHELLOe'

SUGGERIMENTO 4: Finchè c’è una sola sequenza da sostituire, va bene il replace, ma se avessimo un milione di sequenze orribili come &gt;, &#62;, &x3e;, che faremmo? Da bravi data cleaner, possiamo riconoscere che sono sequenze di escape HTML, perciò potremmo usare dei metodi specifici per queste sequenze come html.unescape. Provalo invece del replace e guarda se funziona!

NOTA: Prima di usare il metodo html.unescape, importa il modulo html con il comando:

import html

SUGGERIMENTO 3: Per scrivere n copie di un carattere, usa * come qua:

[17]:
"b" * 3
[17]:
'bbb'
[18]:
"b" * 7
[18]:
'bbbbbbb'

✪✪✪ 1.6 ESERCIZIO: Scrivi qua sotto la soluzione per file a linee immersione-in-python-toc, e prova ad eseguirla premendo Control + Invio:

Mostra soluzione
[19]:
# scrivi qui


   Il vostro primo programma Python  38
      Immersione!  38
      Dichiarare funzioni  41
         Argomenti opzionali e con nome  42
      Scrivere codice leggibile  44
         Stringhe di documentazione  44
      Il percorso di ricerca di import  46
      Ogni cosa è un oggetto  47

2. File CSV

Ci possono essere vari formati per i file tabulari, tra cui sicuramente conoscerai gli Excel (.xls o .xslx). Peccato che se vuoi processare dati programmaticamente, faresti meglio ad evitarli e preferire se possibile i file CSV, letteralmente ‘Comma separated Value’. Per capire il perchè, quando hai tempo potresti guardare questo tutorial in cui si spiega a produttori di dati (in questo caso, dipendenti pubblici) come trasformare Excel in CSV, evidenziando i vari grattacapi che un Excel può presentare a chi poi riusa i dati.

Oggi proveremo ad aprire qualche CSV, prendendo in considerazione i possibili problemi che possono insorgere. I CSV non sono la panacea per tutti i mali, ma offrono maggiore controllo sulla lettura e tipicamente se saltano fuori errori di conversione è perchè siamo stati noi a sbagliare, e non perchè la libreria di lettura ha magari deciso da sola di scambiare nelle date i giorni con i mesi.

Perchè scorrere un CSV ?

Per caricare e processare CSV esistono già librerie molto potenti ed intuitive come Pandas in Python o i dataframe di R, che forse avrai già visto. Oggi invece caricheremo i CSV usando il mezzo più semplice possibile, che è la lettura riga per riga, più o meno come fatto nella prima parte del tutorial. Non bisogna pensare che questo metodo sia primitivo o stupido, a seconda della situazione può salvare la giornata. Come mai? Dato che alcuni file potenzialmente potrebbero occupare terabyte, e nei moderni laptop di solito abbiamo mediamente solo 4 Gigabyte di RAM, che è la memoria dove Python mette le variabili, le funzioni di base di Python per leggere file evitano di caricare tutto in RAM. Tipicamente invece un file viene scorso un po’ alla volta, mettendo in RAM solo una riga alla volta.

DOMANDA 2.1: se vogliamo sapere se un certo file da 100 terabyte contiene almeno 3 milioni di righe in cui è presente la parola ‘ciao’, dobbiamo mettere in RAM contemporaneamente tutte le righe?

Mostra risposta

DOMANDA 2.2: E se volessimo, partendo da un file da 100 terabyte crearne un’altro con gli stessi contenuti del primo file a cui a tutte le righe è aggiunta la parola ‘ciao’ alla fine, dovremmo mettere in RAM contemporaneamente tutte le righe del primo file? E quelle del secondo ?

Mostra risposta

CSV di esempio

Cominciamo con dei CSV artificiali di esempio, sul modello di esercizi già fatti nella introduzione

Per iniziare, vedremo il CSV esempio-1.csv che trovi nella stessa cartella di questo foglio Jupyter. Riportiamo qui il contenuto del file:

animale, anni
cane, 12
gatto, 14
pellicano, 30
scoiattolo, 6
aquila, 25

Notiamo subito che il CSV è più strutturato dei file visti nella sezione precedente

  • 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

Proveremo ora ad importare questo file in Python:

[20]:
import csv
with open('esempio-1.csv', encoding='utf-8', newline='') as f:

    # creiamo un oggetto 'lettore' che pescherà righe dal file
    lettore = csv.reader(f, delimiter=',')

    # 'lettore' è un oggetto cosiddetto 'iterabile', cioè se usato in un for produce una
    #  sequenza di righe dal csv
    #  NOTA: qui ogni riga del file viene convertita in una una lista di stringhe Python!
    for riga in lettore:
        print('Abbiamo appena letto una riga!')
        print(riga)  # stamperà la variabile 'riga', che è una lista di stringhe
        print('')    # stampa una stringa vuota, per separare in verticale
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']

Notiamo subito dall’output della print che viene stampato il file di esempio, ma ci sono delle parentesi quadre ( ‘[]’ ). Cosa significano? Quelle che abbiamo stampato sono una liste di stringhe.

Analizziamo meglio quanto fatto:

import csv

Python è fornito nativamente di un modulo per il trattamento dei csv, col nome intuitivo csv. Con questa istruzione, abbiamo appena caricato questo modulo.

Cosa succede dopo ? Come già fatto per i file a linee in precedenza, apriamo il file in un blocco with:

with open('esempio-1.csv', encoding='utf-8', newline='') as f:
    lettore = csv.reader(f, delimiter=',')
    for riga in lettore:
        print(riga)

Per adesso ignora il newline='' e nota che come prima abbiamo specificato l’encoding

Una volta aperto il file, nella riga

lettore = csv.reader(f, delimiter=',')

chiediamo al modulo csv di crearci un oggetto lettore chiamato lettore per il nostro file, dicendo a Python che il delimitatore per i campi sono le virgole.

NOTA: lettore è il nome di una variabile che stiamo creando, potremmo dare un nome qualunque.

Questo oggetto lettore può essere sfruttato come una specie di generatore di righe usando un ciclo for.

for riga in lettore:
    print(riga)

Nel ciclo for sfruttiamo l’oggetto lettore per iterare nella lettura del file, producendo ad ogni iterazione una riga che chiamiamo riga (ma potrebbe essere un qualunque nome a nostro piacimento). Ad ogni iterazione, la variabile riga viene stampata.

Se guardi bene le stampe delle prime liste, vedrai che ogni volta a ogni riga riga viene assegnata una sola lista Python. La lista contiene tanti elementi quanti campi ci sono nel CSV.

✪ ESERCIZIO 2.3: Riscrivi nella cella qua sotto le istruzioni per leggere e stampare il csv, facendo come sempre attenzione all’indentazione:

Mostra soluzione
[21]:
# scrivi qui


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']

✪✪ ESERCIZIO 2.4: prova a mettere in una variabile listona una lista contenente tutte le righe estratte dal file, che quindi sarà una lista di liste che dovrebbe apparire così.

[['animale', ' anni'],
 ['cane', '12'],
 ['gatto', '14'],
 ['pellicano', '30'],
 ['scoiattolo', '6'],
 ['aquila', '25']]

SUGGERIMENTO: Comincia creando una lista vuota e poi aggiungendo elementi con il metodo .append

Mostra soluzione
[22]:
# scrivi qui


[['animale', 'anni'], ['cane', '12'], ['gatto', '14'], ['pellicano', '30'], ['scoiattolo', '6'], ['aquila', '25']]

✪✪ ESERCIZIO 2.5: Forse avrai notato che i numeri nella liste sono rappresentati come stringhe tipo '12' (nota gli apici), invece che come numeri interi Python (rappresentati senza apici), 12:

Abbiamo appena letto una riga!
['cane', '12']

Quindi, leggendo il file e usando dei normali cicli for, prova a creare nua variabile listona formata come questa, che

  • ha solo i dati, la riga con le intestazioni non è presente

  • i numeri sono rappresentati propriamente come interi

[['cane', 12],
 ['gatto', 14],
 ['pellicano', 30],
 ['scoiattolo', 6],
 ['aquila', 25]]

SUGGERIMENTO 1: per saltare una riga puoi usare l’istruzione next(lettore)

SUGGERIMENTO 2: per convertire una stringa in un intero, si può usare per es. int('25')

Mostra soluzione
[23]:
# scrivi qui


[['cane', 12], ['gatto', 14], ['pellicano', 30], ['scoiattolo', 6], ['aquila', 25]]

Cos’è esattamente lettore ?

Abbiamo detto che lettore genera una sequenza di righe, ed è iterabile. Nel ciclo for, ad ogni ciclo gli chiediamo di leggere una nuova riga, che viene messa nella variabile riga. Quindi possiamo chiederci, cosa succede se stampiamo direttamente lettore, senza usare nessun for ? Vedremo una bella lista o qualcos’altro? Proviamo:

[24]:
import csv
with open('esempio-1.csv', encoding='utf-8', newline='') as f:
    lettore = csv.reader(f, delimiter=',')
    print(lettore)
<_csv.reader object at 0x7f985c63d908>

Quello che vediamo è piuttosto deludente.

✪ ESERCIZIO 2.6: Se ti ricordi, nell’introduzione a un certo punto ci siamo trovati nella stessa situazione, quando abbiamo provato a stampare una map . Che potremmo fare per risolvere la situazione?

Mostra soluzione
[25]:
# scrivi qui


[['animale', 'anni'], ['cane', '12'], ['gatto', '14'], ['pellicano', '30'], ['scoiattolo', '6'], ['aquila', '25']]

Consumare un file

Non tutte le sequenze sono uguali. Da quello che hai visto finora, in Python scorrere un file assomiglia molto a scorrere una lista. Che è molto comodo, ma bisogna stare attenti ad alcune cose. Dato che i file potenzialmente potrebbero occupare terabyte, le funzioni di base di Python per leggere file evitano di caricarli tutti in memoria e tipicamente il file viene scorso un po’ alla volta. Ma se il file non viene caricato tutto nell’ambiente di Python in un colpo solo, cosa succede se proviamo a scorrerlo due volte all’interno della stessa with? E se proviamo ad usarlo fuori dal with, che succede? Guarda i prossimi esercizi per scoprirlo.

✪ ESERCIZIO 2.7: Prendendo la soluzione all’esercizio di prima, prova a chiamare print(list(lettore)) due volte, in sequenza. Ottieni la stessa stampa entrambe le volte?

Mostra soluzione
[26]:
# scrivi qui il codice


[['animale', 'anni'], ['cane', '12'], ['gatto', '14'], ['pellicano', '30'], ['scoiattolo', '6'], ['aquila', '25']]
[]

✪ ESERCIZIO 2.8: Prendendo la soluzione all’esercizio di prima (usando una sola print), prova qua sotto a spostare la print tutta a sinistra (eliminando gli spazi). Funziona ancora?

Mostra soluzione
[27]:
# scrivi qui


✪✪✪ ESERCIZIO 2.9: Adesso che abbiamo capito un po’ che tipo di bestia è lettore, proviamo adesso a produrre questo risultato come già fatto in precedenza, ma usando una list comprehension invece del del ciclo for:

[['cane', 12],
 ['gatto', 14],
 ['pellicano', 30],
 ['scoiattolo', 6],
 ['aquila', 25]]
  • Se riesci, prova anche a scrivere tutta la trasformazione per creare listona in una sola riga, usando la funzione itertools.islice per saltare l’intestazione (per es. itertools.islice(['A', 'B', 'C', 'D', 'E'], 2, None) salta i primi due elementi e produce la sequenza C D E F G - nel nostro caso gli elementi prodotti da lettore sarebbero righe)

Mostra soluzione
[28]:
import csv
import itertools
with open('esempio-1.csv', encoding='utf-8', newline='') as f:
    lettore = csv.reader(f, delimiter=',')
    # scrivi qui


[['cane', 12], ['gatto', 14], ['pellicano', 30], ['scoiattolo', 6], ['aquila', 25]]

✪ ESERCIZIO 2.10: Crea un file mio-esempio.csv nella stessa cartella dove c’è questo foglio Jupyter, copiandoci dentro il contenuto del file esempio-1.csv. Poi aggiungici una colonna descrizione, ricordandoti di separare il nome di colonna dalla precedente con una virgola. Come valori della colonna, metti nelle righe successive stringhe tipo i cani camminano, i pellicani volano a seconda dell’animale, etc ricordandoti di separarle dagli anni usando una virgola, così:

cane,12,i cani camminano

Dopo di che, copia e incolla qua sotto il codice Python per caricare il file, mettendo il nome di file mio-esempio.csv, e prova a caricare il tutto, tanto per vedere se funziona:

[29]:
# scrivi qui
Mostra risposta

✪ ESERCIZIO 2.11: Non tutti i CSV sono strutturati in modo uguale, e a volte quando scriviamo i csv o li importiamo alcuni accorgimenti sono necessari. Cominciamo a vedere che problemi potrebbero sorgere:

  • Nel file, prova a mettere uno o due spazi prima dei numeri, per es scrivi come sotto e guarda che succede:

cane, 12,i cani volano

DOMANDA 2.11.1: Lo spazio viene importato o no?

Mostra risposta

DOMANDA 2.11.2: se convertiamo ad intero, lo spazio è un problema?

Mostra risposta

DOMANDA 2.11.3 Modifica solo la descrizione dei cani da i cani camminano a i cani camminano, ma non volano e prova a rieseguire la cella che legge il file. Che succede?

Mostra risposta

DOMANDA 2.11.4: Per ovviare al problema precedente, una soluzione che si può adottare nei CSV è circondare stringhe contenti virgole da doppi apici, così: "i cani camminano, ma non volano". Funziona ?

Mostra risposta

Leggere come dizionari

Invece di leggere un CSV come se fosse una sequenza di liste, potrebbe essere più conveniente dire a Python di interpretare le linee come se fossero dizionari.

Tramite l’oggetto csv.DictReader sarai in grado di recuperare dizionari, in cui le chiavi saranno 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

La 3.8 è ritornata al vecchio dict perchè nella sua nuova implementazione dei dizionari l’ordine delle chiavi è garantito, quindi sarà consistente con quello degli header del CSV.

[30]:
import csv
with open('esempio-1.csv', encoding='utf-8', newline='') as f:
    lettore = csv.DictReader(f, delimiter=',')   # Notice we now used DictReader
    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'}

Scrivere un CSV

Puoi facilmente 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 !!!

[31]:
import csv

# Per scrivere, RICORDATI di specificare l'opzione 'w'
# ATTENZIONE: 'w' rimpiazza *completamente* eventuali file  esistenti!
with open('file-scritto.csv', 'w', newline='') as csv_da_scrivere:

    scrittore = csv.writer(csv_da_scrivere, delimiter=',')

    scrittore.writerow(['This', 'is', 'a header'])
    scrittore.writerow(['some', 'example', 'data'])
    scrittore.writerow(['some', 'other', 'example data'])

Leggere e scrivere un CSV

Per scrivere un nuovo CSV prendendo dati da un CSV esistente, potresti annidare un with per la lettura dentro uno per la scrittura:

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 !

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

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

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

CSV Impianti funiviari

Di solito sui cataloghi open data come il popolare CKAN (es dati.trentino.it, data.gov.uk, European data portal) i file sono organizzati in dataset, che sono collezioni di risorse: ogni risorsa contiene direttamente un file dentro al catalogo (tipicamente CSV, JSON o XML) oppure un link al file vero e proprio su un server di proprietà dell’organizzazione che ha creato i dati.

Il primo dataset che guarderemo sarà ‘Impianti funiviari in esercizio pubblico:

http://dati.trentino.it/dataset/impianti-funiviari-in-esercizio-pubblico

Qua troverai alcune informazioni generiche sul dataset, di importante nota la licenza che è Creative Commons Zero v1.0, praticamente è la licenza più permissiva che si possa trovare, e garantisce la certezza di poter riusare i dati senza alcun vincolo.

All’interno della pagina del dataset, è presente una risorsa chiamata “Elenco degli impianti bifuni con movimento a va e vieni”:

http://dati.trentino.it/dataset/impianti-funiviari-in-esercizio-pubblico/resource/ceb488a9-696c-4b9d-b8c8-6ca727863931

Alla pagina della risorsa, troviamo un link ad un file CSV (ci si arriva anche cliccando sul bottone blu ‘Vai alla risorsa’):

http://www.sif.provincia.tn.it/binary/pat_sif/opendata/Elenco_degli_impianti_bifuni_con_movimento_a_va_e_vieni_aggiornato_a_marzo_2015.1435662007.csv

Se apro il CSV in una tab di Firefox, sul mio computer che ha sistema operativo Linux, vedo una cosa del genere:

Denominazione impianto,Codice S.I.F.,Comune,Esercente,Data Primo collaudo,Data Ultimo Collaudo,Lunghezza Inclinata (m),Dislivello (m),Stazione di Valle (m.s.l.m.),Capacità Veicolo (pers.),Portata Max (Pers/h),Opere Paravalanghe ,Servizio
Trento - Sardagna,B007e,Trento,Soc. Trentino Trasporti Esercizio,08-apr-64,18-mar-03,1157,400,200,13,270,#,annuale
Mezzocorona - Monte,B008e,Mezzocorona,Soc.Fun. Monte Mezzocorona,22-dic-04,22-dic-04,933,623,267,7,130,si (frane),annuale
Alba - Ciampac,B014m,Canazei,Soc.Fun. Ciampac e Contrin,20-mag-75,27-nov-14,1902,658,1496,75,840,#,bistagionale
Pecol - Col dei Rossi,B017m,Canazei,S.I.T.C.,09-feb-79,05-dic-97,1313,450,1932,76,1100,si,bistagionale
P. S.Pellegrino - Col Margherita,B022m,Moena,Soc.Fun. Col Margherita,11-mar-82,14-dic-01,1395,639,1874,97,1330,#,bistagionale
Vigo di Fassa - Ciampedie,B026m,Vigo di Fassa,Soc.Catinaccio Impianti a Fune,24-lug-85,23-giu-05,1523,570,1431,101,1300,#,bistagionale
Campitello - Col Rodella ,B027m,Canazei,S.I.T.C.,06-dic-86,07-lug-05,2472,984,1411,126,1160,si,bistagionale
Passo Pordoi - Sass Pordoi,B028m,Canazei,S.I.T.C.,23-mag-95,23-mag-95,1487,708,2240,65,820,#,bistagionale
Col Verde - Rosetta,B029b,Tonadico,Imprese e Territorio Soc. Cons. s.r.l.,21-lug-04,21-lug-04,1336,674,1935,41,420,si + M.G.,bistagionale
Tarlenta - Rifugio Mantova,B030g,Peio,Soc.Fun. Peio,30-dic-10,30-dic-10,2856,992,2001,101,860,si + frane,bistagionale

Come atteso, vediamo dei campi separati da virgole.

Problema: caratteri sbagliati ??

Si vede subito un problema nella prima riga delle intestazioni, alla colonna Capacità Veicolo (pers.). Pare che il file abbia la ‘à’ accentata sbagliata. Ma è davvero un problema del file? Vi dico subito di no. Probabilmente, è il server che non sta dicendo a Firefox quale è il giusto encoding per il file. Firefox non ha capacità di divinazione, e fa solo il suo meglio per mostrare il CSV basandosi sulle informazioni che ha, che possono essere limitate e/o addirittura incorrette. Il mondo non è mai come lo vorremmo…

✪ 2.12 ESERCIZIO: scarica il CSV, e prova ad aprirlo in Excel, e / o in LibreOffice Calc. Vedi la à accentata corretta? Se no, prova ad impostare l’encoding (per es. in Calc si chiama ‘Character set’) a ‘Unicode (UTF-8)’

ATTENZIONE SE USI Excel!

Facendo direttamente File->Apri in Excel, probabilmente Excel cercherà di immaginarsi da solo come intabellare il CSV, e sbaglierà metterà tutto le righe in una colonna. Per ovviare al problema, dobbiamo dire ad Excel di mostrare un pannello per chiederci come vogliamo aprire il CSV, facendo così:

  • In Excel vecchi, cerca File-> Importa

  • In Excel recenti, clicca la scheda Dati e poi seleziona Da testo. Per ulteriori riferimenti su Excel, vedere guida di Salvatore Aranzulla

  • NOTA: Casomai il file non fosse disponibile, nella cartella dove c’è questo notebook Jupyter troverai anche lo stesso file rinominato in impianti-bifuni.csv

Esempio di import in LibreOffice Calc-432

Poi dovrebbe risultare più o meno una tabella come questa:

Tabella impianti risalita-42393

Importare in Python

Adesso che abbiamo capito due cose sull’encoding, proviamo ad importare il file in Python (ho rinominato il file originale nel più corto impianti-bifuni.csv):

[34]:
import csv
with open('impianti-bifuni.csv',  encoding='utf-8', newline='') as f:

    # creiamo un oggetto 'lettore' che pescherà righe dal file
    lettore = csv.reader(f, delimiter=',')

    # 'lettore' è un oggetto cosiddetto 'iterabile', cioè se usato in un for produce una
    #  sequenza di righe dal csv
    #  NOTA: qui ogni riga del file viene convertita in una una lista di stringhe Python!
    for riga in lettore:
        print('Abbiamo appena letto una riga!')
        print(riga)  # stamperà la variabile 'riga', che è una lista di stringhe
        print('')    # stampa una stringa vuota, per separare in verticale
Abbiamo appena letto una riga!
['Denominazione impianto', 'Codice S.I.F.', 'Comune', 'Esercente', 'Data Primo collaudo', 'Data Ultimo Collaudo', 'Lunghezza Inclinata (m)', 'Dislivello (m)', 'Stazione di Valle (m.s.l.m.)', 'Capacità Veicolo (pers.)', 'Portata Max (Pers/h)', 'Opere Paravalanghe ', 'Servizio']

Abbiamo appena letto una riga!
['Trento - Sardagna', 'B007e', 'Trento', 'Soc. Trentino Trasporti Esercizio', '08-apr-64', '18-mar-03', '1157', '400', '200', '13', '270', '#', 'annuale']

Abbiamo appena letto una riga!
['Mezzocorona - Monte', 'B008e', 'Mezzocorona', 'Soc.Fun. Monte Mezzocorona', '22-dic-04', '22-dic-04', '933', '623', '267', '7', '130', 'si (frane)', 'annuale']

Abbiamo appena letto una riga!
['Alba - Ciampac', 'B014m', 'Canazei', 'Soc.Fun. Ciampac e Contrin', '20-mag-75', '27-nov-14', '1902', '658', '1496', '75', '840', '#', 'bistagionale']

Abbiamo appena letto una riga!
['Pecol - Col dei Rossi', 'B017m', 'Canazei', 'S.I.T.C.', '09-feb-79', '05-dic-97', '1313', '450', '1932', '76', '1100', 'si', 'bistagionale']

Abbiamo appena letto una riga!
['P. S.Pellegrino - Col Margherita', 'B022m', 'Moena', 'Soc.Fun. Col Margherita', '11-mar-82', '14-dic-01', '1395', '639', '1874', '97', '1330', '#', 'bistagionale']

Abbiamo appena letto una riga!
['Vigo di Fassa - Ciampedie', 'B026m', 'Vigo di Fassa', 'Soc.Catinaccio Impianti a Fune', '24-lug-85', '23-giu-05', '1523', '570', '1431', '101', '1300', '#', 'bistagionale']

Abbiamo appena letto una riga!
['Campitello - Col Rodella ', 'B027m', 'Canazei', 'S.I.T.C.', '06-dic-86', '07-lug-05', '2472', '984', '1411', '126', '1160', 'si', 'bistagionale']

Abbiamo appena letto una riga!
['Passo Pordoi - Sass Pordoi', 'B028m', 'Canazei', 'S.I.T.C.', '23-mag-95', '23-mag-95', '1487', '708', '2240', '65', '820', '#', 'bistagionale']

Abbiamo appena letto una riga!
['Col Verde - Rosetta', 'B029b', 'Tonadico', 'Imprese e Territorio Soc. Cons. s.r.l.', '21-lug-04', '21-lug-04', '1336', '674', '1935', '41', '420', 'si + M.G.', 'bistagionale']

Abbiamo appena letto una riga!
['Tarlenta - Rifugio Mantova', 'B030g', 'Peio', 'Soc.Fun. Peio', '30-dic-10', '30-dic-10', '2856', '992', '2001', '101', '860', 'si + frane', 'bistagionale']

✪✪ ESERCIZIO 2.13 Vediamo che nel dataset alla terza colonna ci sono i comuni dove ci sono gli impianti. Prova a estrarli in una variabile comuni.

  • Attenzione alla prima riga, non vogliamo che la scritta Comune appaia nel risultato

  • Se li metti in una lista, avrai un problema, perchè ci sono comuni che appaiono più volte. Ti conviene quindi creare un insieme vuoto con set([]) e aggiungere elementi con il metodo add (gli insiemi non hanno metodo append). Stampando comuni dovrebbe apparire qualcosa del genere:

{'Vigo di Fassa', 'Trento', 'Mezzocorona', 'Moena', 'Peio', 'Canazei', 'Tonadico'}

NOTA: gli insiemi non hanno ordine, potresti vedere un insieme ordinato diversamente !

Mostra soluzione
[35]:

import csv
with open('impianti-bifuni.csv',  encoding='utf-8', newline='') as f:

    lettore = csv.reader(f, delimiter=',')

    # scrivi qui


{'Tonadico', 'Canazei', 'Moena', 'Trento', 'Mezzocorona', 'Vigo di Fassa', 'Peio'}

✪✪ ESERCIZIO 2.14: prova a conteggiare in un dizionario quanti tipi di servizio vengono trovati. Dovresti ottenere un risultato così:

{
 'bistagionale': 8,
 'annuale': 2
}

SUGGERIMENTO: 'Servizio' è all’ultima posizione, per ottenerla dalla riga puoi usare l’indice -1

Mostra soluzione
[36]:

import csv
with open('impianti-bifuni.csv',  encoding='utf-8', newline='') as f:

    lettore = csv.reader(f, delimiter=',')

    # scrivi qui


{'bistagionale': 8, 'annuale': 2}

Il mistero delle opere paravalanghe

Se guardiamo nella colonna ‘Opere paravalanghe’, notiamo che i valori possibili sono:

  • #

  • si

  • si (frane)

  • si + frane

  • si + M.G

Sono sempre valori testuali, ma pare esserci una qualche regolarità. Visto che il nostro scopo è convertire in oggetti Python i file in ingresso, forse si potrebbero compiere alcune operazioni per semplificare e adattare al meglio questi dati?

✪✪ 2.15 ESERCIZIO: si + frane è la stessa cosa di si (frane) ? Assumendo cha sia vero, prova a stampare righe modificate in cui per uniformare gli elementi uguali a si (frane) settandoli a si + frane. Sia che conosci o non conosci Python, avendo guardato la soluzione della domanda precedente dovresti essere in grado di farlo (ricorda che per modificare un elemento di una lista all’indice N si scrive lista[N] = NUOVO_VALORE )

Scrivi qua sotto la soluzione

Mostra soluzione
[37]:
# scrivi qui


['Tarlenta - Rifugio Mantova', 'B030g', 'Peio', 'Soc.Fun. Peio', '30-dic-10', '30-dic-10', '2856', '992', '2001', '101', '860', 'si + frane', 'bistagionale']
['Tarlenta - Rifugio Mantova', 'B030g', 'Peio', 'Soc.Fun. Peio', '30-dic-10', '30-dic-10', '2856', '992', '2001', '101', '860', 'si + frane', 'bistagionale']
['Tarlenta - Rifugio Mantova', 'B030g', 'Peio', 'Soc.Fun. Peio', '30-dic-10', '30-dic-10', '2856', '992', '2001', '101', '860', 'si + frane', 'bistagionale']
['Tarlenta - Rifugio Mantova', 'B030g', 'Peio', 'Soc.Fun. Peio', '30-dic-10', '30-dic-10', '2856', '992', '2001', '101', '860', 'si + frane', 'bistagionale']
['Tarlenta - Rifugio Mantova', 'B030g', 'Peio', 'Soc.Fun. Peio', '30-dic-10', '30-dic-10', '2856', '992', '2001', '101', '860', 'si + frane', 'bistagionale']
['Tarlenta - Rifugio Mantova', 'B030g', 'Peio', 'Soc.Fun. Peio', '30-dic-10', '30-dic-10', '2856', '992', '2001', '101', '860', 'si + frane', 'bistagionale']
['Tarlenta - Rifugio Mantova', 'B030g', 'Peio', 'Soc.Fun. Peio', '30-dic-10', '30-dic-10', '2856', '992', '2001', '101', '860', 'si + frane', 'bistagionale']
['Tarlenta - Rifugio Mantova', 'B030g', 'Peio', 'Soc.Fun. Peio', '30-dic-10', '30-dic-10', '2856', '992', '2001', '101', '860', 'si + frane', 'bistagionale']
['Tarlenta - Rifugio Mantova', 'B030g', 'Peio', 'Soc.Fun. Peio', '30-dic-10', '30-dic-10', '2856', '992', '2001', '101', '860', 'si + frane', 'bistagionale']
['Tarlenta - Rifugio Mantova', 'B030g', 'Peio', 'Soc.Fun. Peio', '30-dic-10', '30-dic-10', '2856', '992', '2001', '101', '860', 'si + frane', 'bistagionale']

✪ 2.16 DOMANDA: Quel 'M.G' non chiarissimo, come neanche se ci sia qualche differenza tra frane e valanghe. Capire il significato di cosa manipoliamo è importante, forse potresti scoprire il significato nella pagina del dataset ?

Mostra risposta

✪ 2.17 ESERCIZIO: Cosa significa quel cancelletto # ? Conosci un oggetto di Python che potrebbe rappresentarlo ? Riesci a sostituire tutti i cancelletti con quell’oggetto prima di stampare le liste?

Mostra soluzione
[38]:
# scrivi qui


['Trento - Sardagna', 'B007e', 'Trento', 'Soc. Trentino Trasporti Esercizio', '08-apr-64', '18-mar-03', '1157', '400', '200', '13', '270', None, 'annuale']
['Mezzocorona - Monte', 'B008e', 'Mezzocorona', 'Soc.Fun. Monte Mezzocorona', '22-dic-04', '22-dic-04', '933', '623', '267', '7', '130', 'si (frane)', 'annuale']
['Alba - Ciampac', 'B014m', 'Canazei', 'Soc.Fun. Ciampac e Contrin', '20-mag-75', '27-nov-14', '1902', '658', '1496', '75', '840', None, 'bistagionale']
['Pecol - Col dei Rossi', 'B017m', 'Canazei', 'S.I.T.C.', '09-feb-79', '05-dic-97', '1313', '450', '1932', '76', '1100', 'si', 'bistagionale']
['P. S.Pellegrino - Col Margherita', 'B022m', 'Moena', 'Soc.Fun. Col Margherita', '11-mar-82', '14-dic-01', '1395', '639', '1874', '97', '1330', None, 'bistagionale']
['Vigo di Fassa - Ciampedie', 'B026m', 'Vigo di Fassa', 'Soc.Catinaccio Impianti a Fune', '24-lug-85', '23-giu-05', '1523', '570', '1431', '101', '1300', None, 'bistagionale']
['Campitello - Col Rodella ', 'B027m', 'Canazei', 'S.I.T.C.', '06-dic-86', '07-lug-05', '2472', '984', '1411', '126', '1160', 'si', 'bistagionale']
['Passo Pordoi - Sass Pordoi', 'B028m', 'Canazei', 'S.I.T.C.', '23-mag-95', '23-mag-95', '1487', '708', '2240', '65', '820', None, 'bistagionale']
['Col Verde - Rosetta', 'B029b', 'Tonadico', 'Imprese e Territorio Soc. Cons. s.r.l.', '21-lug-04', '21-lug-04', '1336', '674', '1935', '41', '420', 'si + M.G.', 'bistagionale']
['Tarlenta - Rifugio Mantova', 'B030g', 'Peio', 'Soc.Fun. Peio', '30-dic-10', '30-dic-10', '2856', '992', '2001', '101', '860', 'si + frane', 'bistagionale']

✪✪ 2.18 ESERCIZIO: prova a salvare i dati sistemati (quindi con entrambe le traasformazioni si (frane) -> si + frane e # -> None nel NUOVO file impianti-bifuni-sistemato.csv

  • se nella cartella trovi già un file chiamato impianti-bifuni-sistemato.csv, ricordati di cancellarlo manualmente prima di iniziare l’esercizio

  • il valore None come verrà scritto ? Controlla nel file generato dal tuo codice.

Mostra soluzione
[39]:
# scrivi qui



Completata scrittura di:  impianti-bifuni-sistemato.csv

3. File JSON

Il JSON è un formato più elaborato, molto diffuso nel mondo delle applicazioni web.

Un file json è semplicemente un file di testo, strutturato ad albero. Vediamone un esempio, tratto dal dataset stazioni di Bike sharing di Lavis trovato su dati.trentino :

File bike-sharing-lavis.json:

[
  {
    "name": "Grazioli",
    "address": "Piazza Grazioli - Lavis",
    "id": "Grazioli - Lavis",
    "bikes": 3,
    "slots": 7,
    "totalSlots": 10,
    "position": [
      46.139732902099794,
      11.111516155225331
    ]
  },
  {
    "name": "Pressano",
    "address": "Piazza della Croce - Pressano",
    "id": "Pressano - Lavis",
    "bikes": 2,
    "slots": 5,
    "totalSlots": 7,
    "position": [
      46.15368174037716,
      11.106601229430453
    ]
  },
  {
    "name": "Stazione RFI",
    "address": "Via Stazione - Lavis",
    "id": "Stazione RFI - Lavis",
    "bikes": 4,
    "slots": 6,
    "totalSlots": 10,
    "position": [
      46.148180371138814,
      11.096753997622727
    ]
  }
]

Come si può notare, il formato del json è molto simile a strutture dati che già abbiamo in Python, come stringhe, numeri interi, float, liste e dizionari. L’unica differenza sono i campi null in json che diventano None in Python. Quindi la conversione a Python è quasi sempre facile e indolore, e per farla basta usare il modulo già pronto json con la funzione json.load, che interpreta il testo dal file json e lo converte in strutture dati Python:

[40]:
import json

with open('bike-sharing-lavis.json',  encoding='utf-8') as f:
    contenuto_python = json.load(f)

print(contenuto_python)
[{'position': [46.139732902099794, 11.111516155225331], 'bikes': 3, 'name': 'Grazioli', 'slots': 7, 'totalSlots': 10, 'id': 'Grazioli - Lavis', 'address': 'Piazza Grazioli - Lavis'}, {'position': [46.15368174037716, 11.106601229430453], 'bikes': 2, 'name': 'Pressano', 'slots': 5, 'totalSlots': 7, 'id': 'Pressano - Lavis', 'address': 'Piazza della Croce - Pressano'}, {'position': [46.148180371138814, 11.096753997622727], 'bikes': 4, 'name': 'Stazione RFI', 'slots': 6, 'totalSlots': 10, 'id': 'Stazione RFI - Lavis', 'address': 'Via Stazione - Lavis'}]

Notiamo che quanto letto con la funzione json.load non è più semplice testo ma oggetti Python. Per questo json, l’oggetto più esterno è una lista (notate le quadre all’inizio e alla fine del file), e usando type su contenuto_python ne abbiamo la conferma:

[41]:
type(contenuto_python)
[41]:
list

Guardando meglio il JSON, vedrete che è una lista di dizionari. Quindi, per accedere al primo dizionario (cioè quello all0indice zeresimo), possiamo scrivere

[42]:
contenuto_python[0]
[42]:
{'address': 'Piazza Grazioli - Lavis',
 'bikes': 3,
 'id': 'Grazioli - Lavis',
 'name': 'Grazioli',
 'position': [46.139732902099794, 11.111516155225331],
 'slots': 7,
 'totalSlots': 10}

Vediamo che è la stazione in Piazza Grazioli. Per accedere al nome esatto, accederemo alla chiave 'address' del primo dizionario :

[43]:
contenuto_python[0]['address']
[43]:
'Piazza Grazioli - Lavis'

Per accedere alla posizione, usiamo la chiave corrispondente:

[44]:
contenuto_python[0]['position']
[44]:
[46.139732902099794, 11.111516155225331]

Notiamo come essa sia a sua volta una lista. In JSON, possiamo avere alberi ramificati arbitrariamente, senza necessariamente una struttura regolare (per quanto quando generiamo noi un json sia sempre auspicabile mantenere uno schema regolare nei dati).

JSONL

C’è un particolare tipo di file JSON che si chiama JSONL (nota ‘L’ alla fine), e cioè un file di testo contenente una sequenza di linee, ciascuna rappresentante un valido oggetto json.

Guardiamo per esempio il file impiegati.jsonl:

{"nome": "Mario", "cognome":"Rossi"}
{"nome": "Paolo", "cognome":"Bianchi"}
{"nome": "Luca", "cognome":"Verdi"}

Per leggerlo, possiamo aprire il file, separarlo nelle linee di testo e poi interpretare ciascuna come singolo oggetto JSON

[45]:
import json

with open('./impiegati.jsonl', encoding='utf-8',) as f:
    lista_testi_json = list(f)       # converte le linee del file di testo in una lista Python

# in questo caso avremo un contenuto python per ciascuna riga del file originale

i = 0
for testo_json in lista_testi_json:
    contenuto_python = json.loads(testo_json)   # converte testo json in oggetto python
    print('Oggetto ', i)
    print(contenuto_python)
    i = i + 1
Oggetto  0
{'nome': 'Mario', 'cognome': 'Rossi'}
Oggetto  1
{'nome': 'Paolo', 'cognome': 'Bianchi'}
Oggetto  2
{'nome': 'Luca', 'cognome': 'Verdi'}
[ ]: