{ } Programmazione

Espressioni regolari: guida pratica con esempi in Python

Le espressioni regolari con esempi pratici in Python: classi di caratteri, quantificatori, gruppi, ancore, errori comuni e quando è meglio evitarle.

Le espressioni regolari hanno una reputazione ambivalente. Da un lato sono uno strumento potentissimo: in una riga si può cercare, validare o trasformare testo in modi che richiederebbero decine di righe di codice. Dall'altro, un'espressione regolare complessa può diventare illeggibile anche per chi l'ha scritta. Il segreto è conoscerne bene gli elementi di base e usarle per ciò in cui eccellono, senza pretendere che risolvano tutto.

Cos'è un'espressione regolare

Un'espressione regolare, o regex, è un modello che descrive un insieme di stringhe. Il modello gatt[oi] descrive sia «gatto» sia «gatti». Un motore di espressioni regolari confronta il modello con un testo e trova le parti che corrispondono.

In Python si usano tramite il modulo re della libreria standard:

import re

testo = "Ordini: A-1042, B-0077, A-2210"
codici = re.findall(r"[A-Z]-\d{4}", testo)
# ['A-1042', 'B-0077', 'A-2210']

La r prima della stringa indica una raw string: le barre rovesciate vengono passate così come sono al motore delle regex, senza essere interpretate da Python. Usala sempre per le espressioni regolari.

Gli elementi fondamentali

Caratteri e classi

Simbolo Significato Esempio
. Qualunque carattere tranne il ritorno a capo c.sa trova «casa», «cosa»
[abc] Uno dei caratteri indicati [aeiou] trova una vocale
[^abc] Qualunque carattere tranne quelli indicati [^0-9] trova un non numero
\d Una cifra \d\d trova «42»
\w Un carattere di parola (lettere, cifre, underscore) \w+ trova una parola
\s Uno spazio bianco (spazio, tab, a capo) \s+ trova spazi multipli

Quantificatori

Simbolo Significato
* Zero o più volte
+ Una o più volte
? Zero o una volta (elemento facoltativo)
{3} Esattamente tre volte
{2,5} Da due a cinque volte

Ancore

^ indica l'inizio della stringa e $ la fine. \b indica un confine di parola. Senza ancore, una regex che valida un codice postale di cinque cifre, \d{5}, accetterebbe anche «abc123456xyz», perché contiene cinque cifre consecutive. Con le ancore, ^\d{5}$, accetta solo stringhe composte esattamente da cinque cifre.

Gruppi: estrarre le parti

Le parentesi tonde creano gruppi, che permettono di estrarre porzioni del testo trovato:

riga = "2024-03-15 ERROR Connessione al database fallita"
m = re.match(r"(\d{4})-(\d{2})-(\d{2}) (\w+) (.*)", riga)
if m:
    anno, mese, giorno, livello, messaggio = m.groups()

Per espressioni con molti gruppi, i gruppi con nome migliorano molto la leggibilità:

modello = re.compile(
    r"(?P<data>\d{4}-\d{2}-\d{2}) (?P<livello>\w+) (?P<messaggio>.*)"
)
m = modello.match(riga)
print(m["livello"])   # ERROR

re.compile prepara il modello una volta sola: utile quando lo stesso modello viene applicato a molte righe.

Sostituire testo

re.sub sostituisce ogni corrispondenza. Un esempio pratico: normalizzare spazi multipli e mascherare numeri di telefono in un testo da condividere.

pulito = re.sub(r"\s+", " ", testo).strip()
mascherato = re.sub(r"\b(\d{3})\d{4}(\d{3})\b", r"\1****\2", pulito)

Nella stringa di sostituzione, \1 e \2 richiamano il contenuto dei gruppi.

Avidità: l'errore più comune

I quantificatori * e + sono avidi: prendono quanto più testo possibile. Cercando i tag in <b>uno</b> e <b>due</b> con <b>.*</b>, si ottiene un'unica corrispondenza che va dal primo <b> all'ultimo </b>. La versione non avida, <b>.*?</b>, si ferma alla prima chiusura possibile e trova correttamente i due elementi separati.

Quando non usarle

Le espressioni regolari sono ottime per testi con una struttura regolare e piatta: codici, date, righe di log, formati semplici. Diventano la scelta sbagliata in altri casi:

  • HTML e XML: strutture annidate che una regex non gestisce in modo affidabile; usa un parser dedicato;
  • JSON, CSV e formati strutturati: esistono moduli standard (json, csv) che gestiscono correttamente virgolette, caratteri speciali e casi limite;
  • validazione completa delle email: le regole formali sono così complesse che una regex realistica sarà sempre approssimativa; meglio un controllo semplice e una email di conferma;
  • semplici operazioni su stringhe: testo.startswith("http") o "errore" in riga sono più chiari e veloci di una regex.

Esiste anche un rischio di prestazioni: alcune espressioni con quantificatori annidati, come (a+)+$, possono richiedere un tempo enorme su certi input, perché il motore prova moltissime combinazioni. Se una regex viene applicata a testo fornito da utenti, evita queste costruzioni e limita la lunghezza dell'input.

Scriverle in modo leggibile

Il flag re.VERBOSE permette di spezzare un'espressione su più righe e aggiungere commenti, ignorando gli spazi. Per regex non banali è un investimento che ripaga chi dovrà leggerle. E, come per qualunque altro codice, scrivi test con esempi che devono corrispondere e altri che non devono.

In sintesi

  • Un'espressione regolare descrive un insieme di stringhe; in Python si usa con il modulo re e le raw string.
  • Classi di caratteri, quantificatori, ancore e gruppi bastano per la maggior parte dei casi.
  • Usa le ancore ^ e $ per validare, e i quantificatori non avidi per evitare corrispondenze troppo lunghe.
  • Non usarle per HTML, JSON o CSV: esistono parser dedicati più affidabili.
  • Rendile leggibili con gruppi con nome, re.VERBOSE e test con esempi concreti.
MM
Marco Marzaduri

Marco Marzaduri si occupa di cybersecurity, informatica forense e sviluppo web. Laureato in informatica e in psicologia, unisce l'analisi tecnica alla comprensione del fattore umano.

Leggi anche