Salta al contenuto
Note per Studenti File di record e controllo degli errori riga per riga

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

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 da int ma "Rossi\n" finisce nei dati.
  • Una sola try può coprire più conversioni; se serve distinguere il tipo di errore si usano più except: split con troppi o pochi campi e int("abc") sollevano entrambi ValueError, mentre riga.split()[1] su una riga con un solo campo solleva IndexError.
  • I messaggi vanno su sys.stderr, i risultati su sys.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("") e float("1,5") sono ValueError (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.

python
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 →.

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;
}
  • fgets restituisce NULL a fine file; legge al più n - 1 caratteri e tiene il '\n'.
  • strtol(s, &fine, 10) converte e mette in fine il primo carattere non convertito: fine == s significa nessuna cifra, *fine != '\0' significa caratteri in più ("12abc"). atoi non segnala errori e restituisce 0 per "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), mai while (!feof(f)).
  • fopen restituisce NULL se il file non si apre: controllare sempre, e stampare perror.

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 stdout mescolandoli ai risultati.
  • Interrompere tutto alla prima riga sbagliata quando il testo chiede di continuare.
  • In C, usare atoi o ignorare il valore restituito da sscanf e fgets.
  • In Python, un try troppo 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) con except ValueError (campi di numero sbagliato, conversione fallita); IndexError se si indicizza un campo mancante.
  • Errori su sys.stderr (print(..., file=sys.stderr)), risultati su stdout.
  • int("3.5") è errore, float("1,5") anche. Sorgenti: open(...), sys.argv[1], sys.stdin, input() fino a riga vuota (EOFError). FileNotFoundError gestita 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 → NULL a fine file, tiene il '\n' (riga[strcspn(riga, "\n")] = '\0');
  • strtol(s, &fine, 10): fine == s nessuna cifra, *fine != '\0' caratteri in più; atoi non segnala errori;
  • sscanf restituisce i campi letti (confrontare con quelli attesi); fscanf in while (... == 1), mai while (!feof);
  • fopen → NULL se 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.

Esercizi su questo argomento