Python: normalizzazione e pulizia avanzata delle stringhe

by theArchitect
SHARE
Python: normalizzazione e pulizia avanzata delle stringhe
© Guida-HTML5.it

Introduzione

Quando si lavora con stringhe in Python, spesso non basta fare semplici sostituzioni o usare strip() e lower(). Nei progetti reali, i dati testuali arrivano quasi sempre “sporchi”: possono contenere spazi extra, caratteri invisibili, accenti, maiuscole incoerenti, simboli non desiderati o formati diversi per lo stesso contenuto.

Un sotto-argomento molto utile, pratico e spesso sottovalutato è la normalizzazione avanzata delle stringhe. Questo processo serve a rendere testi diversi tra loro più uniformi, così da confrontarli, cercarli, salvarli o validarli in modo affidabile.

In questo tutorial vedremo come costruire una piccola utility Python per:

  • rimuovere spazi e caratteri invisibili;
  • uniformare maiuscole e minuscole;
  • gestire accenti e segni diacritici;
  • trasformare testi in formato “slug” adatto a URL o identificatori;
  • confrontare stringhe in modo più robusto.

Questo approccio è utile in contesti come importazione dati, ricerca testuale, pulizia di input utente, deduplicazione di record e preparazione di contenuti per database o API.

Codice completo

import unicodedata
import re


def rimuovi_caratteri_invisibili(testo: str) -> str:
    """
    Rimuove caratteri invisibili comuni come:
    - zero-width space
    - BOM
    - caratteri di controllo non stampabili
    """
    if testo is None:
        return ""

    # Normalizza eventuali caratteri Unicode equivalenti
    testo = unicodedata.normalize("NFKC", testo)

    # Elimina caratteri di controllo e invisibili
    caratteri_da_rimuovere = [
        "u200b",  # zero width space
        "u200c",  # zero width non-joiner
        "u200d",  # zero width joiner
        "ufeff",  # byte order mark
    ]
    for char in caratteri_da_rimuovere:
        testo = testo.replace(char, "")

    # Rimuove anche altri caratteri di controllo ASCII
    testo = "".join(ch for ch in testo if unicodedata.category(ch)[0] != "C")

    return testo


def normalizza_spazi(testo: str) -> str:
    """
    Converte sequenze di spazi, tab e a capo in un singolo spazio.
    Rimuove gli spazi iniziali e finali.
    """
    if testo is None:
        return ""

    testo = rimuovi_caratteri_invisibili(testo)

    # Sostituisce qualsiasi sequenza di whitespace con un singolo spazio
    testo = re.sub(r"s+", " ", testo)

    return testo.strip()


def rimuovi_accenti(testo: str) -> str:
    """
    Converte caratteri accentati in equivalenti ASCII quando possibile.
    Esempio: ´città´ -> ´citta´
    """
    if testo is None:
        return ""

    testo = unicodedata.normalize("NFD", testo)
    testo = "".join(
        ch for ch in testo
        if unicodedata.category(ch) != "Mn"
    )

    return unicodedata.normalize("NFC", testo)


def normalizza_testo(testo: str, minuscolo: bool = True, senza_accenti: bool = False) -> str:
    """
    Pipeline di normalizzazione:
    - pulizia caratteri invisibili
    - normalizzazione spazi
    - conversione in minuscolo
    - rimozione accenti opzionale
    """
    testo = normalizza_spazi(testo)

    if minuscolo:
        testo = testo.lower()

    if senza_accenti:
        testo = rimuovi_accenti(testo)

    return testo


def slugify(testo: str) -> str:
    """
    Converte una stringa in uno slug semplice:
    - minuscolo
    - senza accenti
    - solo lettere, numeri e trattini
    - spazi convertiti in trattini
    """
    testo = normalizza_testo(testo, minuscolo=True, senza_accenti=True)

    # Mantiene solo lettere, numeri, spazi e trattini
    testo = re.sub(r"[^a-z0-9s-]", "", testo)

    # Converte sequenze di spazi o trattini in un solo trattino
    testo = re.sub(r"[s-]+", "-", testo)

    return testo.strip("-")


def confronta_stringhe(a: str, b: str) -> bool:
    """
    Confronta due stringhe in modo robusto dopo normalizzazione.
    Utile per deduplicazione o matching di input utente.
    """
    return normalizza_testo(a, senza_accenti=True) == normalizza_testo(b, senza_accenti=True)


# -------------------------
# Esempi di utilizzo
# -------------------------

testi = [
    "  Città   di  Milano  ",
    "Cittàu200b di Milano",
    "CITTA DI MILANO",
    "Città-di-Milano!!!",
]

for t in testi:
    print("ORIGINALE:", repr(t))
    print("NORMALIZZATO:", normalizza_testo(t, senza_accenti=True))
    print("SLUG:", slugify(t))
    print("-" * 40)

print(confronta_stringhe("Città di Milano", "citta   di milano"))
print(confronta_stringhe("Pippo", "Pluto"))

Spiegazione

Il codice è organizzato come una piccola “pipeline” di pulizia. Questo è un approccio molto utile perché rende il comportamento chiaro, riutilizzabile e facile da testare.

1. Rimozione dei caratteri invisibili

La funzione rimuovi_caratteri_invisibili() elimina simboli che spesso causano bug difficili da individuare. Un esempio classico è il carattere zero-width space, che sembra non esistere ma altera confronti e ricerche.

In più, la funzione usa unicodedata.category(ch) per scartare caratteri di controllo, cioè quelli appartenenti alla categoria Unicode che inizia con C. Questo aiuta a evitare problemi con input provenienti da copincolla, file esterni o sistemi diversi.

2. Normalizzazione degli spazi

Con normalizza_spazi() convertiamo qualsiasi sequenza di spazi, tab o a capo in un solo spazio. Questo è utile quando il testo proviene da moduli web, OCR, PDF estratti o campi compilati manualmente.

Per farlo usiamo una regex semplice: s+. È una soluzione pratica e leggibile per collassare il whitespace.

3. Rimozione degli accenti

La funzione rimuovi_accenti() usa la normalizzazione Unicode in forma NFD, che separa lettere e segni diacritici. Poi elimina i caratteri con categoria Mn, cioè i segni non combinanti.

Per esempio:

  • città diventa citta
  • è diventa e
  • à diventa a

Questa tecnica è molto utile per confronti “tolleranti”, ma va usata con attenzione: in alcuni contesti gli accenti sono informazione importante e non andrebbero rimossi.

4. Pipeline di normalizzazione

normalizza_testo() combina più passaggi in un’unica funzione. Questo è un buon esempio di design pulito: invece di spargere logica di pulizia in vari punti del programma, centralizziamo tutto in un solo posto.

Il parametro minuscolo permette di decidere se convertire il testo in lowercase, mentre senza_accenti rende la funzione flessibile in base al caso d’uso.

5. Creazione di uno slug

La funzione slugify() trasforma una stringa in un formato adatto a URL, identificatori o nomi file. Per esempio:

  • "Città di Milano""citta-di-milano"
  • "Città-di-Milano!!!""citta-di-milano"

Questo è molto utile per blog, CMS, e-commerce e sistemi di routing web.

6. Confronto robusto tra stringhe

La funzione confronta_stringhe() mostra un caso d’uso concreto: due stringhe che sembrano diverse possono rappresentare lo stesso contenuto. Dopo la normalizzazione, "Città di Milano" e "citta di milano" diventano equivalenti.

Questo è molto utile per deduplicazione di dati, matching di nomi, confronto di etichette e validazione di input.

Best practice

  • Separare i passaggi di pulizia: funzioni piccole e specializzate sono più facili da testare e riusare.
  • Usare Unicode consapevolmente: la gestione dei caratteri internazionali non va trattata come semplice testo ASCII.
  • Non rimuovere gli accenti per default: fallo solo se il caso d’uso lo richiede davvero.
  • Centralizzare la normalizzazione: evita di ripetere la stessa logica in più punti del progetto.
  • Testare con input reali: prova stringhe con spazi multipli, caratteri invisibili, emoji, apostrofi, trattini e testi multilingua.
  • Preferire casefold() quando serve confronto internazionale: in alcuni casi è più adatto di lower() per confronti case-insensitive più robusti.

Un errore comune è “pulire troppo”: ad esempio, rimuovere simboli o accenti senza valutare l’impatto semantico. La normalizzazione deve essere sempre guidata dal contesto.

Riepilogo

La normalizzazione avanzata delle stringhe è una competenza molto pratica in Python. Ti permette di trasformare testi inconsistenti in dati affidabili, confrontabili e riutilizzabili. Nel tutorial abbiamo visto come:

  • eliminare caratteri invisibili e di controllo;
  • uniformare gli spazi;
  • rimuovere accenti quando serve;
  • costruire una pipeline di pulizia;
  • generare slug puliti;
  • confrontare stringhe in modo robusto.

Queste tecniche sono fondamentali in applicazioni reali dove il testo arriva da fonti diverse e non sempre affidabili. Investire in una buona normalizzazione significa ridurre bug, migliorare la qualità dei dati e semplificare il lavoro del resto del programma.

Approfondisci con risorse ufficiali

  • Python Standard Library - unicodedata: documentazione ufficiale per la gestione delle proprietà Unicode.
  • Python Standard Library - re: modulo ufficiale per le espressioni regolari.
  • Python String Methods: elenco completo dei metodi nativi delle stringhe.
  • Unicode Standard: riferimento per capire categorie, normalizzazioni e proprietà dei caratteri.

Se vuoi fare un passo in più, il prossimo argomento utile è costruire una pipeline di validazione testo + normalizzazione + confronto fuzzy, molto usata in motori di ricerca, CRM e sistemi di importazione dati.

SHARE