csv - File con Valori separati da virgola
Scopo: Legge e scrive file con valori separati da virgola
Il modulo csv può essere usato per lavorare con dati esportati da fogli di calcolo e database in file di testo formattati con campi e record, generalmente riferiti come file con formato CSV (comma-separated value) visto che le virgole sono spesso utilizzate come separatore di campo nei record.
Lettura
Si utilizza reader()
(lettore - n.d.t.) per creare un oggetto per leggere dati da un file CSV. Il lettore può essere usato come un iteratore per elaborare in sequenza le righe del file in esame. Ad esempio:
# csv_reader.py
import csv
import sys
with open(sys.argv[1], 'rt') as f:
reader = csv.reader(f)
for row in reader:
print(row)
Il primo argomento di reader()
è la sorgente delle righe di testo. In questo caso si tratta di un file, ma è accettato un qualsiasi iterabile (istanze di StringIO
, liste, ecc.). Altri argomenti opzionali possono essere passati per controllare come vengono elaborati i dati in input.
"Titolo 1","Titolo 2","Titolo 3","Titolo 4" 1,"a",08/18/07,"å" 2,"b",08/19/07,"∫" 3,"c",08/20/07,"ç"
Mano a mano che il file viene letto, ogni riga viene elaborata e convertita in una lista di stringhe.
$ python3 csv_reader.py testdata.csv ['Titolo 1', 'Titolo 2', 'Titolo 3', 'Titolo 4'] ['1', 'a', '08/18/07', 'å'] ['2', 'b', '08/19/07', '∫'] ['3', 'c', '08/20/07', 'ç']
Il parser gestisce le interruzioni di riga inserite all'interno delle stringhe in una riga, che è la ragione per la quale una riga elaborata non sempre è identica a quella di partenza.
"Titolo 1","Titolo 2","Titolo 3" 1,"prima riga seconda riga",08/18/07
I campi in input con interruzioni di riga mantengono le interruzioni di riga interne quando sono restituite dal parser.
$ python3 csv_reader.py testlinebreak.csv ['Titolo 1', 'Titolo 2', 'Titolo 3'] ['1', 'prima riga\nseconda riga', '08/18/07']
Scrittura
Scrivere file CSV è tanto facile quanto leggerli. Si utilizza writer()
per creare un oggetto per la scrittura, poi si itera sulle righe, utilizzando writerow()
per scriverle.
# csv_writer.py
import csv
import sys
unicode_chars = 'å∫ç'
with open(sys.argv[1], 'wt') as f:
writer = csv.writer(f)
writer.writerow(('Titolo 1', 'Titolo 2', 'Titolo 3', 'Titolo 4'))
for i in range(3):
row = (
i + 1,
chr(ord('a') + i),
'08/{:02d}/07'.format(i + 1),
unicode_chars[i],
)
writer.writerow(row)
print(open(sys.argv[1], 'rt').read())
L'output non sembra esattamente uguale a quello dei dati esportati usati nell'esempio di lettura in quanto mancano degli apici attorno a qualche valore.
$ python3 csv_writer.py testout.csv Titolo 1,Titolo 2,Titolo 3,Titolo 4 1,a,08/01/07,å 2,b,08/02/07,∫ 3,c,08/03/07,ç
Racchiudere tra Apici
Il comportamento predefinito per racchiudere i dati tra apici è diverso per la scrittura, quindi i valori delle seconde e terze colonne nell'esempio precedente non sono racchiusi tra apici. Per inserirle si imposta l'argomento quoting ad uno dei quattro differenti tipi:
- QUOTE_ALL
- Racchiude tutto, a prescindere dal tipo.
- QUOTE_MINIMAL
- Racchiude tra apici i campi con caratteri speciali (qualsiasi cosa che potrebbe confondere il parser configurato con lo stesso dialetto ed opzioni). Questa è l'opzione predefinita
- QUOTE_NONNUMERIC
- Racchiude tra apici tutti i campi che non sono interi o float. Quando viene usato in lettura, i campi in input che non sono racchiusi tra apici vengono convertiti in float.
- QUOTE_NONE
- Non racchiude nulla tra apici in scrittura. Quando usato in lettura, i caratteri apici vengono inclusi nei valori di campo (in genere sono invece trattati come delimitatori ed eliminati).
writer = csv.writer(f, quoting=csv.QUOTE_NONNUMERIC)
In questo caso, QUOTE_NONNUMERIC
aggiunge apici attorno alle colonne che contengono valori che non sono numeri.
$ python3 csv_writer_quoted.py testout_quoted.csv "Titolo 1","Titolo 2","Titolo 3","Titolo 4" 1,"a","08/01/07","å" 2,"b","08/02/07","∫" 3,"c","08/03/07","ç"
Dialetti
Non esiste uno standard ben definito per i file CSV, quindi il parser deve essere flessibile. Il che significa che si sono molti parametri che controllano come il modulo csv elabora o scrive i dati. Piuttosto che passare ognuno di questi parametri al lettore ed allo scrittore separatamente, essi sono raggruppati convenientemente in un oggetto dialetto (dialect).
Le classi dialetto possono essere registrate per nome, quindi i chiamanti del modulo csv non hanno bisogno di conoscere in anticipo le impostazioni dei parametri. La lista completa dei dialetti registrati può essere ottenuta con list_dialects()
.
# csv_list_dialects.py
import csv
print(csv.list_dialects())
La libreria standard include 3 dialetti: excel
, excel-tabs
e unix
. Il dialetto excel
funziona con dati esportati nel formato predefinito per Microsoft Excel, e funziona anche con LibreOffice. Il dialetto unix
utilizza doppi apici per racchiudere tutti i campi e \n
come separatore di record.
$ python3 csv_list_dialects.py ['excel-tab', 'unix', 'excel']
Creare un Dialetto
Si supponga che, invece di usare le virgole per delimitare i campi, il file di input usi il carattere |
, tipo questo:
"Titolo 1"|"Titolo 2"|"Titolo 3"
1|"prima riga
seconda riga"|08/18/07
un nuovo dialetto può essere registrato, utilizzando il delimitatore appropriato.
# csv_dialect.py
import csv
csv.register_dialect('pipes', delimiter='|')
with open('testdata.pipes', 'r') as f:
reader = csv.reader(f, dialect='pipes')
for row in reader:
print(row)
Utilizzando il dialetto "pipes", si può leggere il file allo stesso modo di uno delimitato da virgole.
$ python3 csv_dialect.py ['Titolo 1', 'Titolo 2', 'Titolo 3'] ['1', 'prima riga\nseconda riga', '08/18/07']
Parametri del Dialetto
Un dialetto specifica tutti i token utilizzati per la lettura e scrittura di un file. La tabella seguente elenca gli aspetti del formato del file che possono essere specificati, dal modo in cui le colonne sono delimitate al carattere di escape utilizzato per un token.
ATTRIBUTO | PREDEFINITO | DESCRIZIONE |
---|---|---|
delimiter | , |
Separatore di campo (un carattere). |
doublequote | True |
Flag che controlla se le istanze di quotechar debbano essere raddoppiate. |
escapechar | None |
Carattere utilizzato per indicare una sequenza di escape. |
lineterminator | \r\n |
Stringa utilizzata dal writer per terminare una riga |
quotechar | " |
Stringa con la quale racchiudere i campi che contengono valori speciali (un carattere). |
quoting | QUOTE_MINIMAL |
Controlla il comportamento di utilizzo del carattere utilizzato per racchiudere i campi descritto in precedenza. |
skipinitialspace | False |
Ignora lo spazio dopo il delimitatore di campo. |
# csv_dialect_variations.py
import csv
import sys
csv.register_dialect('escaped',
escapechar='\\',
doublequote=False,
quoting=csv.QUOTE_NONE,
)
csv.register_dialect('singlequote',
quotechar="'",
quoting=csv.QUOTE_ALL,
)
quoting_modes = {
getattr(csv, n): n
for n in dir(csv)
if n.startswith('QUOTE_')
}
TEMPLATE = '''\
Dialetto: "{name}"
delimiter = {dl!r:<6} skipinitialspace = {si!r}
doublequote = {dq!r:<6} quoting = {qu}
quotechar = {qc!r:<6} lineterminator = {lt!r}
escapechar = {ec!r:<6}
'''
for name in sorted(csv.list_dialects()):
dialect = csv.get_dialect(name)
print(TEMPLATE.format(
name=name,
dl=dialect.delimiter,
si=dialect.skipinitialspace,
dq=dialect.doublequote,
qu=quoting_modes[dialect.quoting],
qc=dialect.quotechar,
lt=dialect.lineterminator,
ec=dialect.escapechar,
))
writer = csv.writer(sys.stdout, dialect=dialect)
writer.writerow(
('col1', 1, '10/01/2010',
'Caratteri speciali: " \' {} da elaborare'.format(
dialect.delimiter))
)
print()
Il programma mostra come gli stessi dati appaiono quando sono formattati utilizzando diversi dialetti differenti.
$ python3 csv_dialect_variations.py Dialetto: "escaped" delimiter = ',' skipinitialspace = 0 doublequote = 0 quoting = QUOTE_NONE quotechar = '"' lineterminator = '\r\n' escapechar = '\\' col1,1,10/01/2010,Caratteri speciali: \" ' \, da elaborare Dialetto: "excel" delimiter = ',' skipinitialspace = 0 doublequote = 1 quoting = QUOTE_MINIMAL quotechar = '"' lineterminator = '\r\n' escapechar = None col1,1,10/01/2010,"Caratteri speciali: "" ' , da elaborare" Dialetto: "excel-tab" delimiter = '\t' skipinitialspace = 0 doublequote = 1 quoting = QUOTE_MINIMAL quotechar = '"' lineterminator = '\r\n' escapechar = None col1 1 10/01/2010 "Caratteri speciali: "" ' da elaborare" Dialetto: "singlequote" delimiter = ',' skipinitialspace = 0 doublequote = 1 quoting = QUOTE_ALL quotechar = "'" lineterminator = '\r\n' escapechar = None 'col1','1','10/01/2010','Caratteri speciali: " '' , da elaborare' Dialetto: "unix" delimiter = ',' skipinitialspace = 0 doublequote = 1 quoting = QUOTE_ALL quotechar = '"' lineterminator = '\n' escapechar = None "col1","1","10/01/2010","Caratteri speciali: "" ' , da elaborare"
Rilevare Automaticamente i Dialetti
Il modo migliore di configurare un dialetto per l'elaborazione di un file in input è sapere le corrette impostazioni in anticipo. Per dati dove i parametri del dialetto sono sconosciuti, si può usare la classe Sniffer
per fare un'ipotesi plausibile. Il metodo sniff()
ottiene un campione dei dati in input ed un argomento opzionale che fornisce i possibili caratteri di delimitazione.
# csv_dialect_sniffer.py
import csv
from io import StringIO
import textwrap
csv.register_dialect('escaped',
escapechar='\\',
doublequote=False,
quoting=csv.QUOTE_NONE)
csv.register_dialect('singlequote',
quotechar="'",
quoting=csv.QUOTE_ALL)
# Genera dati campione per tutti i dialetti conosciuti
samples = []
for name in sorted(csv.list_dialects()):
buffer = StringIO()
dialect = csv.get_dialect(name)
writer = csv.writer(buffer, dialect=dialect)
writer.writerow(
('col1', 1, '10/01/2010',
'Caratteri speciali " \' {} da elaborare'.format(
dialect.delimiter))
)
samples.append((name, dialect, buffer.getvalue()))
# Indovina il dialetto per un dato campione, quindi utilizza i risultati per
# elaborare i dati.
sniffer = csv.Sniffer()
for name, expected, sample in samples:
print('Dialetto: "{}"'.format(name))
print('In: {}'.format(sample.rstrip()))
dialect = sniffer.sniff(sample, delimiters=',\t')
reader = csv.reader(StringIO(sample), dialect=dialect)
print('Elaborati:\n {}\n'.format(
'\n '.join(repr(r) for r in next(reader))))
Il metodo sniff()
ritorna una istanza di Dialect
con le impostazioni da utilizzare per elaborare i dati. I risultati non sono sempre perfetti, come dimostrato dal dialetto "escaped" nell'esempio.
$ python3 csv_dialect_sniffer.py Dialetto: "escaped" In: col1,1,10/01/2010,Caratteri speciali \" ' \, da elaborare Elaborati: 'col1' '1' '10/01/2010' 'Caratteri speciali \\" \' \\' ' da elaborare' Dialetto: "excel" In: col1,1,10/01/2010,"Caratteri speciali "" ' , da elaborare" Elaborati: 'col1' '1' '10/01/2010' 'Caratteri speciali " \' , da elaborare' Dialetto: "excel-tab" In: col1 1 10/01/2010 "Caratteri speciali "" ' da elaborare" Elaborati: 'col1' '1' '10/01/2010' 'Caratteri speciali " \' \t da elaborare' Dialetto: "singlequote" In: 'col1','1','10/01/2010','Caratteri speciali " '' , da elaborare' Elaborati: 'col1' '1' '10/01/2010' 'Caratteri speciali " \' , da elaborare' Dialetto: "unix" In: "col1","1","10/01/2010","Caratteri speciali "" ' , da elaborare" Elaborati: 'col1' '1' '10/01/2010' 'Caratteri speciali " \' , da elaborare'
Utilizzare Nomi di Campo
Oltre a lavorare con sequenze di dati, il modulo csv include classi per lavorare con righe come dizionari in modo che si possa attribuire un nome ai campi. Le classi DictReader
e DictWriter
trascodificano le righe in dizionari invece che in liste. Le chiavi per il dizionario possono essere passate oppure ricavate per inferenza dalla prima riga dell'input (quando la riga contiene le intestazioni di colonna).
# csv_dictreader.py
import csv
import sys
with open(sys.argv[1], 'rt') as f:
reader = csv.DictReader(f)
for row in reader:
print(row)
Gli oggetti di lettura e scrittura basati sul dizionario sono implementati come wrapper attorno alle classi corrispondenti basate sui dati in sequenza, ed usano gli stessi argomenti e metodi. L'unica differenza è che le righe sono dizionari invece che liste di tuple nell'API di lettura.
$ python3 csv_dictreader.py testdata.csv {'Titolo 4': 'å', 'Titolo 1': '1', 'Titolo 3': '08/18/07', 'Titolo 2': 'a'} {'Titolo 4': '∫', 'Titolo 1': '2', 'Titolo 3': '08/19/07', 'Titolo 2': 'b'} {'Titolo 4': 'ç', 'Titolo 1': '3', 'Titolo 3': '08/20/07', 'Titolo 2': 'c'}
Si devono passare a DictWriter
un elenco di nomi di campo così che possa conoscere come ordinare le colonne nella scrittura del file.
# csv_dictwriter.py
import csv
import sys
fieldnames = ('Titolo 1', 'Titolo 2', 'Titolo 3', 'Titolo 4')
headers = {
n: n
for n in fieldnames
}
unicode_chars = 'å∫ç'
with open(sys.argv[1], 'wt') as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
for i in range(3):
writer.writerow({
'Titolo 1': i + 1,
'Titolo 2': chr(ord('a') + i),
'Titolo 3': '08/{:02d}/07'.format(i + 1),
'Titolo 4': unicode_chars[i],
})
print(open(sys.argv[1], 'rt').read())
I nomi dei campi non sono scritti automaticamente nel file, ma possono essere scritti esplicitamente utilizzando il metodo writeheader()
.
$ python3 csv_dictwriter.py testout.csv Titolo 1,Titolo 2,Titolo 3,Titolo 4 1,a,08/01/07,å 2,b,08/02/07,∫ 3,c,08/03/07,ç
Vedere anche:
- csv
- La documentazione della libreria standard per questo modulo.
- PEP 305
- L'API per i file CSV
- Note di portabilità