Le espressioni regolari (o regex) sono uno strumento potente per la manipolazione e l’analisi del testo. Questa guida ti porterà dai concetti fondamentali alle tecniche più avanzate, con esempi pratici in Python ad ogni passo.
Parte 1: Fondamenti delle Espressioni Regolari
1.1 Che cosa sono le espressioni regolari?
Le espressioni regolari sono sequenze di caratteri che definiscono un modello di ricerca. Sono utilizzate per trovare, estrarre o sostituire testo che corrisponde a determinati criteri.
In Python, le regex sono implementate tramite il modulo re, che dobbiamo importare per utilizzarle:
import re
La funzione più semplice: re.search()
La funzione più basilare è re.search(), che cerca un pattern all’interno di una stringa:
import re
# Cerchiamo la parola "Python" in una frase
testo = "Sto imparando Python per l'analisi dei dati"
risultato = re.search("Python", testo)
if risultato:
print("Pattern trovato!")
print(f"Inizia alla posizione: {risultato.start()}")
print(f"Termina alla posizione: {risultato.end()}")
print(f"Testo trovato: {risultato.group()}")
else:
print("Pattern non trovato")
Output:
Pattern trovato!
Inizia alla posizione: 14
Termina alla posizione: 20
Testo trovato: Python
Questo esempio cerca la parola esatta “Python” nel testo e, se la trova, mostra informazioni sulla posizione e il contenuto del match.
Caratteri speciali basilari
Le espressioni regolari hanno alcuni caratteri speciali che hanno significati particolari:
.– corrisponde a qualsiasi carattere (tranne newline)^– corrisponde all’inizio della stringa$– corrisponde alla fine della stringa*– corrisponde a 0 o più ripetizioni+– corrisponde a 1 o più ripetizioni?– corrisponde a 0 o 1 ripetizione|– alternativa (OR)
Ecco un esempio:
import re # Il punto "." corrisponde a qualsiasi carattere testo = "Python è un linguaggio di programmazione" risultato = re.search("P.thon", testo) print(risultato.group()) # "^" corrisponde all'inizio della stringa risultato = re.search("^Python", testo) print(risultato.group()) # "$" corrisponde alla fine della stringa testo2 = "Mi piace programmare in Python" risultato = re.search("Python$", testo2) print(risultato.group())
Output:
Python
Python
Python
Principali Funzioni del Modulo re
re.match() vs re.search()
re.match() cerca solo all’inizio della stringa, mentre re.search() cerca in tutta la stringa:
import re testo = "Python è fantastico, Python è potente" # match cerca solo all'inizio della stringa risultato_match = re.match("Python", testo) print(risultato_match.group()) # search cerca in tutta la stringa risultato_search = re.search("potente", testo) print(risultato_search.group()) # match fallisce se il pattern non è all'inizio risultato_match_fail = re.match("potente", testo) print(risultato_match_fail)
Output:
Python
potente
None
Caratteri speciali e classi di caratteri:
- Classi di caratteri:
\d: Corrisponde a qualsiasi cifra (0-9).\w: Corrisponde a qualsiasi carattere alfanumerico (lettere, cifre e underscore).\s: Corrisponde a qualsiasi spazio bianco (spazi, tabulazioni, newline).\D,\W,\S: Le versioni maiuscole di queste classi corrispondono ai caratteri non inclusi nelle classi minuscole.
Esempi:
import re testo = "Il prezzo è 123.45 €" cifre = re.findall(r"\d+", testo) # Trova tutte le sequenze di cifre print(cifre) # Output: ['123', '45'] testo2 = "Parola_123" alfanumerici = re.findall(r"\w+", testo2) # Trova tutte le sequenze alfanumeriche print(alfanumerici) # Output: ['Parola_123'] testo3 = "Testo con spazi\ttabulazioni\nnewline" spazi = re.findall(r"\s+", testo3) # Trova tutti gli spazi bianchi print(spazi) # Output: [' ', '\t', '\n']
cifre = re.findall(r"\d+", testo):
-
re.findall(): Questa funzione cerca tutte le occorrenze di un pattern in una stringa e restituisce una lista di corrispondenze. -
r"\d+": Questa è un’espressione regolare “raw string”.\d: Questo carattere speciale corrisponde a qualsiasi cifra (0-9).+: Questo quantificatore significa “uno o più” delle occorrenze del carattere precedente. Quindi,\d+corrisponde a una o più cifre consecutive.
-
In sintesi, questa riga cerca tutte le sequenze di una o più cifre nel testo.
Quantificatori avanzati:
{n}: Corrisponde esattamente a n ripetizioni.{n,}: Corrisponde a n o più ripetizioni.{n,m}: Corrisponde a un minimo di n e un massimo di m ripetizioni.*?,+?,??: Le versioni “lazy” (non avide) dei quantificatori*,+,?. Corrispondono al minor numero possibile di caratteri.
Esempi:
import re
testo = "aaaaabbbccc"
tre_a = re.search(r"a{3}", testo) # Trova esattamente tre 'a'
print(tre_a.group()) # Output: aaa
da_due_a_quattro_b = re.search(r"b{2,4}", testo) # Trova da due a quattro 'b'
print(da_due_a_quattro_b.group()) # Output: bbb
testo2 = "TestoAltro testoAncora testo"
tag_avidi = re.findall(r".*", testo2) # Corrispondenza avida
tag_non_avidi = re.findall(r".*?", testo2) # Corrispondenza non avida
print(tag_avidi) # Output: ['TestoAltro testoAncora testo']
print(tag_non_avidi) # Output: ['Testo', 'Ancora testo']
Flag di compilazione:
re.IGNORECASE(ore.I): Ignora la differenza tra maiuscole e minuscole.re.MULTILINE(ore.M): Permette a^e$di corrispondere all’inizio e alla fine di ogni riga (non solo dell’intera stringa).re.DOTALL(ore.S): Permette a.di corrispondere anche al carattere newline.re.VERBOSE(ore.X): Permette di scrivere regex più leggibili con spazi e commenti.
Esempi:
import re
testo = "Python python PYTHON"
trova_python_maiuscolo_minuscolo = re.findall(r"python", testo, re.IGNORECASE)
print(trova_python_maiuscolo_minuscolo) # Output: ['Python', 'python', 'PYTHON']
testo2 = "Riga 1\nRiga 2\nRiga 3"
righe_che_iniziano_per_r = re.findall(r"^Riga", testo2, re.MULTILINE)
print(righe_che_iniziano_per_r) # Output: ['Riga', 'Riga', 'Riga']
testo3 = "Testo\ncon\nnewline"
tutto = re.findall(r".+", testo3, re.DOTALL)
print(tutto) # Output: ['Testo\ncon\nnewline']
pattern = re.compile(r"""
\d{3} # Tre cifre
- # Un trattino
\d{4} # Quattro cifre
""", re.VERBOSE)
numero_di_telefono = pattern.search("123-4567")
print(numero_di_telefono.group()) #output 123-4567
Cos’è .group()?
Il metodo .group() è una funzione fondamentale degli oggetti “match” che vengono restituiti dalle funzioni del modulo re come:
re.search()re.match()- e dagli iteratori restituiti da
re.finditer()
In parole semplici, quando una funzione regex trova una corrispondenza (un “match”) in una stringa, essa restituisce un oggetto “match”. Questo oggetto contiene informazioni sul match trovato, e .group() è il metodo che ti permette di accedere alle parti di quel match.
Utilizzo di base di .group(): L’intero match
Nella sua forma più semplice, quando chiami .group() senza argomenti (o con group(0)), ti restituisce l’intera stringa che ha corrisposto al pattern.
Ecco un esempio
import re
testo = "Sto imparando Python per l'analisi dei dati"
risultato = re.search("Python", testo)
if risultato:
parola_trovata = risultato.group() # Equivalente a risultato.group(0)
print(f"Testo trovato: {parola_trovata}")
else:
print("Pattern non trovato")
Output:
Testo trovato: Python
In questo caso, poiché il pattern era semplicemente "Python", .group() restituisce esattamente "Python", che è la parte del testo che ha combaciato con il pattern.
.group(n) e i Gruppi di Cattura (Capture Groups)
La vera potenza di .group() si rivela quando usi i gruppi di cattura nelle tue espressioni regolari.
I gruppi di cattura sono creati utilizzando le parentesi tonde () all’interno del pattern regex.
Le parentesi fanno due cose importanti:
- Raggruppano: Definiscono una “sottosezione” del pattern.
- Catturano: Salvare la porzione di testo che corrisponde a quella sottosezione.
Quando hai gruppi di cattura, puoi usare .group(n) per accedere al testo catturato da ciascun gruppo, dove n è il numero del gruppo (partendo da 1).
Esempio con gruppi di cattura:
Consideriamo l’esempio dell’estrazione della data:
import re
testo = "Data di nascita: 23/05/1990"
data_pattern = r"(\d{2})/(\d{2})/(\d{4})" # Tre gruppi di cattura per GG, MM, AAAA
risultato = re.search(data_pattern, testo)
if risultato:
giorno = risultato.group(1) # Gruppo 1: (\d{2}) - il giorno
mese = risultato.group(2) # Gruppo 2: (\d{2}) - il mese
anno = risultato.group(3) # Gruppo 3: (\d{4}) - l'anno
data_completa = risultato.group(0) # Gruppo 0: l'intero match
print(f"Data completa trovata: {data_completa}")
print(f"Giorno: {giorno}")
print(f"Mese: {mese}")
print(f"Anno: {anno}")
else:
print("Data non trovata")
Output:
Data completa trovata: 23/05/1990
Giorno: 23
Mese: 05
Anno: 1990
Spiegazione:
data_pattern = r"(\d{2})/(\d{2})/(\d{4})": Abbiamo definito un pattern con tre gruppi di cattura, ciascuno racchiuso tra parentesi tonde.- Il primo gruppo
(\d{2})cattura due cifre (il giorno). - Il secondo gruppo
(\d{2})cattura altre due cifre (il mese). - Il terzo gruppo
(\d{4})cattura quattro cifre (l’anno).
- Il primo gruppo
risultato.group(1),risultato.group(2),risultato.group(3): Ci permettono di accedere al testo specifico catturato da ciascuno dei tre gruppi definiti nel pattern.risultato.group(0): Restituisce, come sempre, l’intera stringa che ha combaciato con il pattern completo, in questo caso “23/05/1990”.
.group(0): Sempre l’intero match
È importante ricordare che .group(0) o .group() senza argomenti restituiscono sempre l’intero match, anche quando ci sono gruppi di cattura. Il gruppo 0 rappresenta l’intera corrispondenza del pattern, mentre i gruppi da 1 in su rappresentano le corrispondenze delle parti racchiuse tra parentesi.
Gruppi Nominati e .group('nome')
Puoi anche dare dei nomi ai gruppi di cattura utilizzando la sintassi (?P<nome>pattern). In questo caso, invece di usare .group(numero), puoi usare .group('nome') per accedere al contenuto catturato dal gruppo nominato.
Esempio con gruppi nominati:
iimport re email = "[email protected]" pattern = r"(?P[a-zA-Z0-9.]+)@(?P[a-zA-Z0-9.]+\.[a-zA-Z]{2,})" match = re.search(pattern, email) if match: username = match.group('username') # Accedi al gruppo nominato 'username' dominio = match.group('dominio') # Accedi al gruppo nominato 'dominio' print(f"Username: {username}") print(f"Dominio: {dominio}")
Output:
Username: utente.esempio
Dominio: dominio.com
Qui, match.group('username') accede direttamente al contenuto catturato dal gruppo nominato "username", rendendo il codice più leggibile e meno dipendente dalla posizione numerica dei gruppi.
.groups() (al plurale)
È utile menzionare anche il metodo .groups() (al plurale, con la ‘s’ finale). A differenza di .group(), .groups() restituisce una tupla contenente tutti i contenuti catturati da tutti i gruppi di cattura (esclusi i gruppi nominati se usi .groups() senza argomenti aggiuntivi; ci sono modi per includerli, ma sono un po’ più avanzati).
Esempio con .groups():
import re
testo = "Data di nascita: 23/05/1990"
data_pattern = r"(\d{2})/(\d{2})/(\d{4})"
risultato = re.search(data_pattern, testo)
if risultato:
gruppi = risultato.groups() # Restituisce una tupla con ('23', '05', '1990')
print(f"Tupla dei gruppi: {gruppi}")
giorno, mese, anno = risultato.groups() # Unpacking della tupla
print(f"Giorno: {giorno}, Mese: {mese}, Anno: {anno}")
Output:
Tupla dei gruppi: ('23', '05', '1990')
Giorno: 23, Mese: 05, Anno: 1990
In sintesi:
.group()o.group(0): Restituisce l’intero match..group(n)(conn >= 1): Restituisce il contenuto catturato dall’n-esimo gruppo di cattura (in base all’ordine delle parentesi aperte nel pattern)..group('nome'): Restituisce il contenuto catturato dal gruppo di cattura nominato"nome"..groups(): Restituisce una tupla contenente il contenuto di tutti i gruppi di cattura (in ordine numerico).
In questa prima parte del nostro tutorial, abbiamo gettato le basi per comprendere le espressioni regolari in Python. Abbiamo esplorato le funzioni fondamentali del modulo re, come re.search(), re.match() e re.findall(), e abbiamo imparato a utilizzare i caratteri speciali di base e le classi di caratteri per creare pattern di ricerca efficaci.
Abbiamo anche approfondito il funzionamento del metodo .group(), essenziale per estrarre le parti di testo che corrispondono ai nostri pattern, e abbiamo introdotto i gruppi di cattura, sia numerati che nominati, che ci permettono di segmentare e manipolare i risultati delle nostre ricerche.
Gli esempi pratici che abbiamo visto dimostrano come le espressioni regolari possano essere utilizzate per risolvere problemi concreti di analisi e manipolazione del testo.
Questo è solo l’inizio del nostro viaggio nel mondo delle regex. Nelle prossime parti del tutorial, esploreremo quantificatori più avanzati, flag di compilazione, e tecniche per gestire scenari di ricerca più complessi. Impareremo anche a utilizzare le regex per validare dati, estrarre informazioni da documenti e molto altro ancora.
Espressioni Regolari (Regex) in Python: Esercizi Pratici e Soluzioni Dettagliate





