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 rigasono 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
ree 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.VERBOSEe test con esempi concreti.