File di record e controllo degli errori riga per riga
In questa pagina 4
Molti programmi leggono dati strutturati: nome:costo, (tempo valore), matricola nome cognome. Il compito è sempre lo stesso: per ogni riga estrarre i campi, convertirli, e se la riga non va bene dirlo (di solito con il numero di riga) senza fermare il programma. Per le basi su open e with vedi File di testo in Pythonopen con modalità e codifica, il costrutto with, lettura riga per riga, read e readlines, scrittura con write e print, percorsi relativi, file CSV con il modulo csv.File di testo in Python →; per le eccezioni Eccezioni in PythonEccezioni e traceback, eccezioni predefinite più comuni, try/except/else/finally, raise per segnalare errori, propagazione lungo le chiamate.Eccezioni in Python →.
Schema in Python
import io, sys
def leggi_record(f):
"""Legge righe 'nome:costo'. Restituisce la lista di (nome, costo) valide
e segnala le altre su stderr, con il numero di riga."""
buoni = []
for n, riga in enumerate(f, start=1): # n parte da 1
riga = riga.strip()
if not riga: # riga vuota: si salta
continue
try:
nome, costo = riga.split(":") # ValueError se i campi non sono 2
buoni.append((nome.strip(), int(costo))) # ValueError se non è un intero
except ValueError:
print(f"riga {n}: formato errato", file=sys.stderr)
return buoni
dati = io.StringIO("Rossi:12\n\nBianchi:9\nVerdi\nNeri:abc\n Gialli : 30 \n")
print(leggi_record(dati)) # [('Rossi', 12), ('Bianchi', 9), ('Gialli', 30)]enumerate(f, start=1)dà il numero di riga senza contatore a mano.strip()toglie il'\n'finale e gli spazi; senza,"12\n"è accettato daintma"Rossi\n"finisce nei dati.- Una sola
trypuò coprire più conversioni; se serve distinguere il tipo di errore si usano piùexcept:splitcon troppi o pochi campi eint("abc")sollevano entrambiValueError, mentreriga.split()[1]su una riga con un solo campo sollevaIndexError. - I messaggi vanno su
sys.stderr, i risultati susys.stdout: così si possono separare con le ridirezioni della shell (2> errori.txt). int("3.5")è un errore,float("3.5")no: scegliere la conversione dal tipo del campo;float("")efloat("1,5")sonoValueError(la virgola decimale non esiste).
Sorgenti diverse
| Sorgente | Come |
|---|---|
| file con nome fisso | with open("dati.txt") as f: |
| nome da riga di comando | with open(sys.argv[1]) as f: (controllare len(sys.argv)) |
| standard input | for riga in sys.stdin: (fine input con Ctrl+D su Linux/macOS, Ctrl+Z e Invio su Windows) |
| fino a una riga vuota | while (riga := input()) != "": e EOFError se l'input finisce prima |
Un file che non esiste solleva FileNotFoundError: va gestita attorno all'open, non dentro il ciclo.
def formato_tv(riga):
"""'(3 25.5)' -> (3, 25.5)"""
t, v = riga.strip().strip("()").split()
return int(t), float(v)
print(formato_tv("(3 25.5)")) # (3, 25.5)Schema in C
In C non ci sono eccezioni: si controlla il valore restituito di ogni funzione. Per i dettagli vedi Input e output in Cprintf con larghezza e precisione, scanf con indirizzi e valore di ritorno, lettura di righe con fgets, file con FILE*, fopen, fclose, fprintf, fscanf, controllo degli errori e della fine del file.Input e output in C → e Array e stringhe in CArray di dimensione fissa in memoria contigua, inizializzazione, nessun controllo sugli indici, passaggio a funzioni con la lunghezza, matrici; stringhe come array di char terminati da '\0' e funzioni di string.h.Array e stringhe in C →.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
/* legge righe "nome:costo"; restituisce quante sono valide */
int leggi(FILE *f, char nomi[][32], int costi[], int max) {
char riga[128];
int n = 0, nriga = 0;
while (fgets(riga, sizeof riga, f) != NULL) { /* NULL: fine file */
nriga++;
riga[strcspn(riga, "\n")] = '\0'; /* toglie il '\n' */
if (riga[0] == '\0')
continue;
char *due_punti = strchr(riga, ':');
if (due_punti == NULL) {
fprintf(stderr, "riga %d: formato errato\n", nriga);
continue;
}
*due_punti = '\0'; /* riga = nome, due_punti+1 = costo */
char *fine;
errno = 0;
long c = strtol(due_punti + 1, &fine, 10);
if (fine == due_punti + 1 || *fine != '\0' || errno != 0) {
fprintf(stderr, "riga %d: costo non valido\n", nriga);
continue;
}
if (n < max && strlen(riga) < 32) {
strcpy(nomi[n], riga);
costi[n++] = (int)c;
}
}
return n;
}fgetsrestituisceNULLa fine file; legge al piùn - 1caratteri e tiene il'\n'.strtol(s, &fine, 10)converte e mette infineil primo carattere non convertito:fine == ssignifica nessuna cifra,*fine != '\0'significa caratteri in più ("12abc").atoinon segnala errori e restituisce0per"abc": da evitare per dati non fidati.sscanf(riga, "%31[^:]:%d", nome, &costo)è più corto ma non distingue"Rossi:12abc"da"Rossi:12"; restituisce il numero di campi letti, da confrontare con quelli attesi (== 2).- Con
fscanf(f, "%d", &x)si legge un numero alla volta: il ciclo èwhile (fscanf(...) == 1), maiwhile (!feof(f)). fopenrestituisceNULLse il file non si apre: controllare sempre, e stampareperror.
Errori tipici
- Non togliere il
'\n'e confrontare stringhe che non coincidono mai. - Contare le righe a partire da 0 nei messaggi.
- Stampare gli errori su
stdoutmescolandoli ai risultati. - Interrompere tutto alla prima riga sbagliata quando il testo chiede di continuare.
- In C, usare
atoio ignorare il valore restituito dasscanfefgets. - In Python, un
trytroppo largo (tutto il ciclo): la prima riga errata esce dal ciclo invece di essere solo saltata.
Versione ripasso
Per ogni riga: estrarre i campi, convertirli, e se la riga non va bene segnalarlo con il numero di riga senza fermarsi (File di testo in Pythonopen con modalità e codifica, il costrutto with, lettura riga per riga, read e readlines, scrittura con write e print, percorsi relativi, file CSV con il modulo csv.File di testo in Python →, Eccezioni in PythonEccezioni e traceback, eccezioni predefinite più comuni, try/except/else/finally, raise per segnalare errori, propagazione lungo le chiamate.Eccezioni in Python →).
Python
for n, riga in enumerate(f, start=1);riga.strip(); riga vuota →continue.try: nome, costo = riga.split(":"); int(costo)conexcept ValueError(campi di numero sbagliato, conversione fallita);IndexErrorse si indicizza un campo mancante.- Errori su
sys.stderr(print(..., file=sys.stderr)), risultati sustdout. int("3.5")è errore,float("1,5")anche. Sorgenti:open(...),sys.argv[1],sys.stdin,input()fino a riga vuota (EOFError).FileNotFoundErrorgestita attorno all'open."(3 25.5)"→riga.strip("()").split()→int,float.
C
Si controlla il valore restituito (Input e output in Cprintf con larghezza e precisione, scanf con indirizzi e valore di ritorno, lettura di righe con fgets, file con FILE*, fopen, fclose, fprintf, fscanf, controllo degli errori e della fine del file.Input e output in C →, Array e stringhe in CArray di dimensione fissa in memoria contigua, inizializzazione, nessun controllo sugli indici, passaggio a funzioni con la lunghezza, matrici; stringhe come array di char terminati da '\0' e funzioni di string.h.Array e stringhe in C →):
fgets→NULLa fine file, tiene il'\n'(riga[strcspn(riga, "\n")] = '\0');strtol(s, &fine, 10):fine == snessuna cifra,*fine != '\0'caratteri in più;atoinon segnala errori;sscanfrestituisce i campi letti (confrontare con quelli attesi);fscanfinwhile (... == 1), maiwhile (!feof);fopen→NULLse il file non si apre.
Errori tipici: '\n' non tolto; righe contate da 0; errori su stdout; stop alla prima riga errata; try troppo largo; atoi.