Pagine

giovedì 8 dicembre 2011

Python ed i CSV

Premessa


I CSV sono semplicissimi files di testo e sono un primo esempio di database sebbene con delle limitazioni. L'acronimo significa Comma Separated Value ovvero "Valori Separati da una Virgola". In sostanza un file CSV si può aprire con qualsiasi editor di "testo semplice" in qualsiasi sistema operativo e si presenta come qualcosa del genere:

Essi, come detto prima, sono un primo esempio di database sebbene la struttura non possa essere complessa data la natura del supporto.

 

Vantaggi e svantaggi nell'uso dei CSV


I database ma anche un semplice foglio di calcolo, offrono performance, personalizzazioni, decisamente maggiori rispetto ai CSV.
Per questi motivi molte persone pensano, a torto, che il sistema CSV, data la sua semplicità intrinseca sia ormai obsoleto.
Ma allora chi usa e perchè viene usato questo formato?
I CSV, a mio avviso, hanno due enormi vantaggi.
Da una parte permettono di immagazzinare quantità abnormi di dati: un foglio di Excel, ad esempio, ha un limite numerico di righe e colonne mentre CSV non soffre di questo limite: esso può immagazzinare "virtualmente" infinite colonne ed infinite righe. 
Ovviamente il limite esiste ma dipende dalla memoria RAM e dal file system e non dal supporto in sè.
L'altro vantaggio è la portabilità del formato: la maggior parte dei fogli di calcolo (si pensi MS Excel) e server sql (si pensi ad AS400 o MS SQLServer) sono proprietari e girano su uno o pochi sistemi operativi; i CSV, al contrario, essendo file di testo possono essere letti e scritti da qualsiasi software su qualsiasi sistema operativo: questo fattore li rende degli alleati insostituibili che permettono il dialogo tra sistemi anche estremamente differenti tra loro.
Facciamo, dunque, uno schemino in breve dei vantaggi/svantaggi.
Partiamo ad evidenziarne gli svantaggi:
  • non gestisce le relazioni fra tabelle
  • non permette la creazione di viste, stored procedure e tutte le altre facilitazioni proprie dei server sql
  • non permette la gestione della multiutenza: può accedere solo un programma/persona per volta.
E, successivamente i vantaggi:
  • formato universale: si possono leggere e scrivere file CSV con qualsiasi software su qualsiasi sistema operativo
  • permette di gestire "infinite" quantità di dati senza limiti di colonne (campi) e/o righe (record).

 

Dialetti

Ciascun CSV può differenziarsi per il "dialetto" utilizzato.
Un dialetto viene identificato, prevalentemente da tre parametri:
  1. il separatore tra un campo e l'altro: in America è di default la virgola (,) e da qui il nome "Comma". Nei paese non anglosassoni, Italia inclusa, poichè la virgola è il separatore tra le cifre decimali di un numero, si utilizza il punto e virgola (;) come nell'esempio precedente.
  2. il separatore tra un record e l'altro: solitamente ogni volta che si va a capo, si identifica un nuovo record. Questa scelta generalmente accettata da tutti può, all'occorrenza, essere modificata con un altro carattere. Si ricorda che il carattere a capo è identificato da "\n" su linux/unix e da "\r\n" sui sistemi windows.
  3. il quoting: ci si riferisce alle virgolette singole (') o le virgolette doppie ("); vengono utilizzate per racchiudere un testo. Sono particolarmente utili quando si pensa di voler utilizzare all'interno di un campo anche il separatore.
Una delle prime cose da fare, dunque, quando si lavora con i CSV in python è definire il dialetto che viene utilizzato.
Il modulo CSV ha già un paio di dialetti preimpostati che sono quelli utilizzati da excel ma, all'uopo, se ne può impostare di personalizzati.

 

Script di esempio

Prima di creare questo script devi creare il file dati.csv e dati2.csv con le seguenti righe.
Dati.csv:
1
2
3
4
5
6
7
8
CodiceAnagrafico;Provvigioni;Montante;Mese
52593;552;11040;11
53704;2784,6;32760;11
53756;387;7740;11
48779;1510,2;37320;6
23922;0;16320;8
"52592";"1320";"26400";11
"53440";"1578,96";20640;11
link - raw This paste is brought you by Friendpaste.


Dat2.csv
1
2
3
4
5
6
7
8
"CodiceAnagrafico";"Provvigioni";"Montante";"Mese"
52593;552;11040;11
53704;2784,6;32760;11
53756;387;7740;11
48779;1510,2;37320;6
23922;0;16320;8
52592;1320;26400;11
53440;1578,96;20640;11
link - raw This paste is brought you by Friendpaste.


Ed infine lo script:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
## ATTENZIONE: Per questo esercizio bisogna avere nella stessa directory
## il file denominato "dati.csv" e "dati2.csv" con i dati a fine script

import sys
import csv
import os

#################################################
# DIALETTI e QUOTING
#################################################

## --------------- I parametri dei dialetti

## Un dialetto si specifica con tutti i parametri utili per fare
## l'analisi in lettura e scrittura.
## Ciascun aspetto del formato del file puo' essere specificato:
## Attributo( Valore di Default) --> Significato
## delimiter ( , ) --> E' il separatore di campo (un solo carattere)
## doublequote ( True ) --> Flag che controlla se il quotechar e' doppio o no
## escapechar ( None ) --> Carattere usato per indicare un escape
## lineterminator ( \r\n ) --> Stringa usata dal writer per terminare una linea
## quotechar ( " ) --> Indica che i campi contengono stringa (un solo carattere)
## quoting ( QUOTE_MINIMAL ) --> Controlla il comportamento del quoting
## skipinitialspace ( False ) --> Ignora gli spazi bianchi dopo il delimitatore di campo


## --------------- Quoting

## Spendiamo due parole in piu' sul quoting.
## Ci sono 4 differenti opzioni definite come costanti nel modulo csv:
## QUOTE_ALL: quota tutto, indipendentemente dal tipo di dato.
## QUOTE_MINIMAL: quota i campi con caratteri speciali (qualsiasicosa possa essere confuso dal parser). Questo e' il default.
## QUOTE_NONNUMERIC: quota tutti i campi che non hanno un tipo di dato integer o float. Il reader converte i campi non quotati in float.
## QUOTE_NONE: Non quota nulla nel writer. Nel reader, invece, il carattere di quoting e' inglobato nel campo.



# Vediamo la lista dei dialetti:
print "-"*50
print "Lista dei dialetti"
print csv.list_dialects()
print

print "-"*50
print "Creiamo alcuni dialetti"
# Creiamo dei dialetti a seconda dei parametri
# Il dialetto denominato "europeo" in seguito e' quello usato di solito da noi italiani
csv.register_dialect('europeo', delimiter=';', quotechar='"', quoting=csv.QUOTE_MINIMAL)
csv.register_dialect('escaped', escapechar='\\', doublequote=False, quoting=csv.QUOTE_NONE)
csv.register_dialect('singlequote', quotechar="'", quoting=csv.QUOTE_ALL)
print

print "-"*50
print "Rivediamo la lista dei dialetti"
# Rivediamo la lista dei dialetti:
print csv.list_dialects()

raw_input("Premi invio per continuare...")
print
print
print


#################################################
# Oggetto READER (SENZA DIALETTO)
#################################################
filecsv = open("dati.csv","rt")
lettore=csv.reader(filecsv)

print "-"*50
print "Stampo la lista dei records letti senza dialetto:"
for riga in lettore:
print riga

filecsv.close()

stringa='''Come si vede dall'output dei record, questo e' un disastro!

1) Elementi non riconosciuti
['23922;0;16320;8'] risulta essere un unico elemento di array e non 4 differenti elementi distinti.
Questo e' dovuto al mancato riconoscimento del delimitatore.

2) Quoting non riconosciuto
['52592;"1320";"26400";11'] non riconosce le virgolette come quoting

3) Delimitatore errato
['53440;"1578', '96";20640;11'] si nota come il sistema riconosca due elementi nell'array.
Questi sono dovuti al riconoscimento di una virgola che, per noi e' il separatore dei decimali
mentre per lo script e' il delimitatore di campo. Ne consegue un errore fondamentale e la necessita'
di specificare il delimitatore.
'''

print stringa
raw_input("Premi invio per continuare...")
print
print
print


#################################################
# Oggetto READER (CON DIALETTO)
#################################################
## A questo punto bisogna sistemare il file dati.csv.
## In particolare e' necessario fare particolarmente attenzione alla virgola: bisogna
## modificare il delimitatore dalla virgola al punto e virgola.
## Di conseguenza se, nel file, ci sono dei ; sara' necessario, prima, eliminarli magari sostituendoli con un punto.
## Andiamo ad analizzare il file dati2.csv che sarebbe il file dati.csv modificato

print "-"*50
filecsv = open("dati.csv","rt")
lettore=csv.reader(filecsv, dialect="europeo")
print "Stampo la lista dei records letti con dialetto:"
for riga in lettore:
print riga

filecsv.close()

print "A questo punto tutto sembra andare meglio"
raw_input("Premi invio per continuare...")
print
print
print


#################################################
# Oggetto READER (CON DIALETTO USANDO I NOMI DI CAMPO)
#################################################
## A questo punto una funzionalita' particolarmente interessante ed importante
## e' rappresentata dalla possibilita' di identificare i campi non tanto
## come l'elemento numero 0,1,2.... bensi' con il loro nome...
## A questo scopo ci viene in aiuto DictReader
## Questo oggetto preleva le intestazioni della prima riga e le ripropone come
## intestazione del nome del campo nelle successive.
## IMPORTANTISSIMO!!
## Vediamo come...

print "-"*50
filecsv = open("dati.csv","rt")
lettore=csv.DictReader(filecsv, dialect="europeo")
print "Stampo la lista dei records letti con dialetto e intestazioni di campo:"
for riga in lettore:
print riga

filecsv.close()
raw_input("Premi invio per continuare...")
print
print
print


#################################################
# Oggetto WRITER (SENZA DIALETTO)
#################################################
## A questo punto proviamo a stampare un file CSV usando l'oggetto writer
## Non usiamo alcun dialetto ma la modalita' sarebbe la stessa che nel reader.
print "-"*50
print "Creaiamo il file dati3.csv"
filecsv = open("dati3.csv", 'wt')
writer = csv.writer(filecsv)
writer.writerow( ('Title 1', 'Title 2', 'Title 3') )

for i in range(10):
writer.writerow( (i+1, chr(ord('a') + i), '08/%02d/07' % (i+1)) )

filecsv.close()
print
print
print
link - raw This paste is brought you by Friendpaste.

1 commento:

  1. Salve, ho letto con piacere il suo articolo, mi sto immergendo da poco nel python, e avevo bisogno di trovare una soluzione per convertire una colonna specifica in riga, da un file sorgente ad un secondo file di output, ma non so come iniziare.

    grazie

    RispondiElimina