Python RegEx
Impara le espressioni regolari Python: sintassi, sequenze speciali, gruppi, lookahead, flag e funzioni del modulo re con esempi chiari.
Le espressioni regolari (regex) ti permettono di cercare, estrarre e sostituire testo in base a pattern flessibili anziché stringhe esatte. Il modulo integrato re di Python fornisce tutto il necessario. Questo capitolo copre l'intero toolkit regex: sintassi, sequenze speciali, quantificatori, gruppi, lookahead/lookbehind, flag e ogni funzione chiave di re — con esempi corretti ed eseguibili.
Perché usare le espressioni regolari?
I metodi string semplici (str.find(), str.replace(), str.split()) funzionano bene per testo fisso. Le espressioni regolari eccellono quando il pattern varia:
- Verificare che una string assomigli a un indirizzo email o numero di telefono.
- Estrarre tutte le date da un documento, indipendentemente dal formato esatto.
- Rimuovere i tag HTML da una string.
- Sostituire più sequenze di spazi bianchi diverse con un singolo spazio.
Quando il compito riguarda descrivere una forma piuttosto che un valore fisso, ricorri a re.
Raw String
I pattern regex sono quasi sempre scritti come raw string (r'...'). Le raw string trattano i backslash come caratteri letterali, il che è importante perché la regex usa molte sequenze con backslash (\d, \w, \s, \b). Senza il prefisso r occorrerebbero doppi backslash ovunque:
import re
# Both patterns are identical — raw string is easier to read
re.findall(r'\d+', 'abc 123') # raw: r'\d+'
re.findall('\\d+', 'abc 123') # normal: '\\d+'Usa le raw string per ogni pattern regex — è la convenzione universale.
Sintassi di base: i metacaratteri
I metacaratteri sono caratteri con significato speciale all'interno di un pattern. I caratteri letterali corrispondono esattamente a se stessi.
| Metacarattere | Significato |
|---|---|
. | Qualsiasi carattere tranne un'a capo |
^ | Inizio della string (o della riga in modalità MULTILINE) |
$ | Fine della string (o della riga in modalità MULTILINE) |
* | Zero o più occorrenze dell'elemento precedente |
+ | Una o più occorrenze dell'elemento precedente |
? | Zero o una occorrenza dell'elemento precedente |
{n} | Esattamente n ripetizioni |
{n,m} | Tra n e m ripetizioni |
[...] | Classe di caratteri — uno qualsiasi dei caratteri elencati |
[^...] | Classe negata — qualsiasi carattere non elencato |
| | Alternanza — l'espressione sinistra o quella destra |
() | Gruppo di cattura |
\ | Escape di un metacarattere, o inizio di una sequenza speciale |
Per corrispondere a un metacarattere letterale come . o *, aggiunge un backslash davanti: \. corrisponde a un punto reale.
Sequenze speciali
Le sequenze speciali sono classi di caratteri abbreviate che compaiono frequentemente nei pattern reali.
| Sequenza | Corrisponde a |
|---|---|
\d | Qualsiasi cifra — equivale a [0-9] |
\D | Qualsiasi non-cifra |
\w | Carattere alfanumerico: lettere, cifre, underscore |
\W | Carattere non alfanumerico |
\s | Spazio bianco: spazio, tab, a capo |
\S | Non-spazio bianco |
\b | Confine di parola (zero-width) |
\B | Non-confine |
import re
print(re.findall(r'\d+', 'I have 3 cats and 12 dogs'))
# ['3', '12']
print(re.findall(r'\w+', 'hello_world 123'))
# ['hello_world', '123']
print(re.findall(r'\bPython\b', 'Python Pythonista Python3'))
# ['Python'] — word boundary prevents partial matches\b è particolarmente utile: corrisponde alla posizione tra un carattere alfanumerico e uno non alfanumerico, quindi \bPython\b corrisponde alla parola standalone "Python" ma non a "Pythonista" o "Python3".
Quantificatori
I quantificatori controllano quante volte deve corrispondere l'elemento precedente.
import re
print(re.findall(r'a*', 'baaa')) # ['', 'aaa', '']
print(re.findall(r'a+', 'baaa')) # ['aaa']
print(re.findall(r'a?', 'baaa')) # ['', 'a', 'a', 'a', '']
print(re.findall(r'a{3}', 'baaa')) # ['aaa']Corrispondenza greedy vs lazy
Per impostazione predefinita, i quantificatori sono greedy — corrispondono al massimo testo possibile. Aggiungi ? dopo il quantificatore per renderlo lazy (corrisponde al minimo possibile).
import re
html = '<b>bold</b> and <i>italic</i>'
print(re.findall(r'<.*>', html))
# ['<b>bold</b> and <i>italic</i>'] — greedy: matches from first < to last >
print(re.findall(r'<.*?>', html))
# ['<b>', '</b>', '<i>', '</i>'] — lazy: matches each individual tagI quantificatori lazy sono essenziali quando si analizza testo strutturato come HTML o frammenti JSON.
Classi di caratteri
Una classe di caratteri [...] corrisponde a uno qualsiasi dei caratteri dell'insieme specificato. Usa - per gli intervalli e ^ all'inizio per negare la classe.
import re
print(re.findall(r'[aeiou]', 'hello world'))
# ['e', 'o', 'o']
print(re.findall(r'[0-9]', 'a1b2c3'))
# ['1', '2', '3']
print(re.findall(r'[^aeiou\s]+', 'hello world'))
# ['h', 'll', 'w', 'rld'] — consonants only (not vowels, not spaces)Intervalli pronti all'uso comuni: [a-z] lettere minuscole, [A-Z] maiuscole, [0-9] cifre, [a-zA-Z0-9] alfanumerici.
Ancore
Le ancore non consumano caratteri — asseriscono una posizione nella string.
import re
print(re.findall(r'^Python', 'Python is great'))
# ['Python'] — matches only if 'Python' is at the start
print(re.findall(r'great$', 'Python is great'))
# ['great'] — matches only if 'great' is at the end
print(re.findall(r'^Python', 'Learn Python'))
# [] — 'Python' is not at the start of this stringConsulta il flag re.MULTILINE più avanti in questo capitolo per applicare ^ e $ per riga anziché per string.
Alternanza
Il pipe | funziona come un OR logico tra due espressioni.
import re
print(re.findall(r'cat|dog', 'I have a cat and a dog'))
# ['cat', 'dog']
print(re.findall(r'colou?r|colour', 'color and colour'))
# ['color', 'colour']Gruppi di cattura
Le parentesi () creano un gruppo di cattura. I gruppi ti permettono di estrarre sottoparte di una corrispondenza. re.search() restituisce un oggetto match; chiama .group(n) o .groups() su di esso.
import re
match = re.search(r'(\d{4})-(\d{2})-(\d{2})', '2023-10-05')
if match:
print(match.group(0)) # '2023-10-05' — entire match
print(match.group(1)) # '2023'
print(match.groups()) # ('2023', '10', '05')Gruppi nominati
Assegna un nome a un gruppo con (?P<name>...) per accedervi tramite nome anziché posizione. Questo rende i pattern molto più facili da mantenere.
import re
match = re.search(
r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})',
'2023-10-05'
)
if match:
print(match.group('year')) # '2023'
print(match.groupdict()) # {'year': '2023', 'month': '10', 'day': '05'}Gruppi non-catturanti
Usa (?:...) quando hai bisogno di raggruppare ma non di catturare il valore.
import re
print(re.findall(r'(?:Mr|Mrs|Ms)\.? \w+', 'Mr. Smith and Mrs. Jones'))
# ['Mr. Smith', 'Mrs. Jones']Lookahead e Lookbehind
Il lookahead ((?=...)) e il lookbehind ((?<=...)) asseriscono che qualcosa segua o preceda la corrispondenza, senza includerlo nel risultato. Sono zero-width: non consumano caratteri.
import re
# Positive lookahead — find numbers followed by 'dollars'
print(re.findall(r'\d+(?= dollars)', '100 dollars and 200 euros'))
# ['100']
# Negative lookahead — find numbers NOT followed by 'dollars'
print(re.findall(r'\b\d+\b(?! dollars)', '100 dollars and 200 euros'))
# ['200']
# Positive lookbehind — extract domain from email addresses
emails = 'Contact [email protected] or [email protected]'
print(re.findall(r'(?<=@)\w+\.\w+', emails))
# ['example.com', 'test.org']I pattern lookbehind devono avere una larghezza fissa in Python — non puoi usare * o + al loro interno.
Il modulo re: funzioni principali
re.search() — Trova la prima corrispondenza
Restituisce un oggetto match per la prima posizione in cui il pattern corrisponde, oppure None se non c'è alcuna corrispondenza.
import re
text = 'apple banana apple'
match = re.search(r'banana', text)
if match:
print(match.group()) # 'banana'
print(match.span()) # (6, 12)re.match() — Corrispondenza solo all'inizio
Come re.search(), ma il pattern deve corrispondere all'inizio della string.
import re
print(bool(re.match(r'\d+', 'abc123'))) # False — no digit at start
print(bool(re.search(r'\d+', 'abc123'))) # True — digit found anywhereUsa re.search() quando vuoi trovare un pattern ovunque; usa re.match() quando il pattern deve apparire all'inizio.
re.fullmatch() — Corrispondenza dell'intera string
Il pattern deve corrispondere all'intera string dall'inizio alla fine.
import re
print(bool(re.fullmatch(r'\d{5}', '12345'))) # True — exactly 5 digits
print(bool(re.fullmatch(r'\d{5}', '123456'))) # False — too long
print(bool(re.fullmatch(r'\d{5}', '1234X'))) # False — non-digit presentre.fullmatch() è ideale per la validazione dell'input (codici postali, numeri di telefono, ecc.).
re.findall() — Tutte le corrispondenze non sovrapposte
Restituisce una lista di tutte le corrispondenze. Se il pattern ha gruppi, restituisce una lista di tuple.
import re
print(re.findall(r'\d+', 'abc 123 def 456'))
# ['123', '456']
# With a group — returns list of group values
print(re.findall(r'(\w+)@(\w+\.\w+)', '[email protected] [email protected]'))
# [('alice', 'example.com'), ('bob', 'test.org')]re.finditer() — Iteratore di oggetti match
Come re.findall(), ma restituisce oggetti match uno alla volta. Utile per testi lunghi o quando hai bisogno delle informazioni sullo span.
import re
for m in re.finditer(r'\d+', 'abc 123 def 456'):
print(m.group(), m.start(), m.end())
# 123 4 7
# 456 12 15re.sub() — Sostituire le corrispondenze
Sostituisce ogni occorrenza del pattern con una string di sostituzione o il valore restituito da una funzione.
import re
text = 'apple banana apple'
print(re.sub(r'apple', 'orange', text))
# 'orange banana orange'
# Limit replacements
print(re.sub(r'apple', 'orange', text, count=1))
# 'orange banana apple'
# Backreferences in replacement — reformat a date
print(re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\3/\2/\1', '2023-10-05'))
# '05/10/2023're.split() — Dividere per pattern
Divide la string a ogni occorrenza del pattern.
import re
print(re.split(r',\s*', 'apple, banana, cherry, date'))
# ['apple', 'banana', 'cherry', 'date']
# Limit the number of splits
print(re.split(r',\s*', 'apple, banana, cherry, date', maxsplit=2))
# ['apple', 'banana', 'cherry, date']Compilare i pattern con re.compile()
Quando usi lo stesso pattern molte volte, compilalo una sola volta con re.compile() per migliorare le prestazioni. L'oggetto pattern risultante ha tutti gli stessi metodi (findall, search, sub, ecc.).
import re
# Compile once
digit_pattern = re.compile(r'\d+')
# Reuse many times
print(digit_pattern.findall('abc 123 def 456')) # ['123', '456']
print(digit_pattern.sub('NUM', 'abc 123 def 456')) # 'abc NUM def NUM'
print(digit_pattern.search('xyz 99')) # <re.Match object ...>La compilazione è particolarmente vantaggiosa all'interno di cicli o funzioni chiamate frequentemente.
Flag
I flag modificano il comportamento della corrispondenza. Passali come ultimo argomento a qualsiasi funzione re, oppure includili quando chiami re.compile(). Più flag possono essere combinati con |.
| Flag | Forma breve | Effetto |
|---|---|---|
re.IGNORECASE | re.I | Corrispondenza senza distinzione di maiuscole/minuscole |
re.MULTILINE | re.M | ^ e $ corrispondono all'inizio/fine di ogni riga |
re.DOTALL | re.S | . corrisponde anche ai caratteri di a capo |
re.VERBOSE | re.X | Consente spazi bianchi e commenti all'interno del pattern |
import re
# IGNORECASE
print(re.findall(r'hello', 'Hello HELLO hello', re.IGNORECASE))
# ['Hello', 'HELLO', 'hello']
# MULTILINE — ^ matches the start of each line
text = 'first line\nsecond line\nthird line'
print(re.findall(r'^\w+', text, re.MULTILINE))
# ['first', 'second', 'third']
# DOTALL — . matches newline characters
print(re.findall(r'<.*?>', '<div>\n<p>text</p>\n</div>', re.DOTALL))
# ['<div>', '<p>', '</p>', '</div>']
# VERBOSE — write readable patterns with comments
date_pattern = re.compile(r'''
(?P<year>\d{4}) # four-digit year
-
(?P<month>\d{2}) # two-digit month
-
(?P<day>\d{2}) # two-digit day
''', re.VERBOSE)
print(date_pattern.search('2023-10-05').groupdict())
# {'year': '2023', 'month': '10', 'day': '05'}Escape dell'input utente con re.escape()
Se costruisci un pattern da testo fornito dall'utente, eseguine sempre l'escape per evitare un'interpretazione involontaria dei metacaratteri.
import re
user_input = 'hello.world'
# Without escaping, '.' matches any character
# With escaping, '\.' matches a literal dot
safe_pattern = re.escape(user_input)
print(safe_pattern) # 'hello\\.world'
print(bool(re.search(safe_pattern, 'say hello.world'))) # True
print(bool(re.search(safe_pattern, 'say helloXworld'))) # FalseEsempi pratici
Validare un indirizzo email
import re
def is_valid_email(email):
pattern = r'^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$'
return bool(re.fullmatch(pattern, email))
print(is_valid_email('[email protected]')) # True
print(is_valid_email('bad-email@')) # FalseEstrarre tutti gli URL dal testo
import re
text = 'Visit https://www.example.com or http://test.org for details.'
urls = re.findall(r'https?://[\w./-]+', text)
print(urls)
# ['https://www.example.com', 'http://test.org']Rimuovere gli spazi bianchi in eccesso
import re
messy = ' hello world Python '
clean = re.sub(r'\s+', ' ', messy).strip()
print(clean) # 'hello world Python'Analizzare una riga di log
import re
log = '2023-10-05 14:32:11 ERROR Failed to connect to database'
pattern = re.compile(
r'(?P<date>\d{4}-\d{2}-\d{2}) '
r'(?P<time>\d{2}:\d{2}:\d{2}) '
r'(?P<level>\w+) '
r'(?P<message>.+)'
)
m = pattern.match(log)
if m:
print(m.group('level')) # 'ERROR'
print(m.group('message')) # 'Failed to connect to database'Errori comuni
re.match() vs re.search() — re.match() controlla solo l'inizio della string. I nuovi utenti spesso si aspettano che cerchi ovunque e rimangono confusi quando restituisce None.
Dimenticare le raw string — '\d' in una normale string Python è semplicemente 'd' con un escape non riconosciuto (o un SyntaxWarning in Python 3.12+). Scrivi sempre r'\d'.
Corrispondenze greedy che consumano troppo — Se un pattern .* cattura più di quanto intendevi, passa a .*? (lazy).
Caratteri speciali nelle string di sostituzione — In re.sub(), le sequenze con backslash come \1 nella sostituzione si riferiscono ai gruppi catturati. Per includere un backslash letterale nella sostituzione, scrivi \\.
re.findall() con gruppi — Quando il tuo pattern contiene gruppi, re.findall() restituisce i gruppi, non la corrispondenza completa. Usa un gruppo non-catturante (?:...) se vuoi la corrispondenza completa.
Riferimento rapido
| Operazione | Funzione |
|---|---|
| Trova prima corrispondenza | re.search(pattern, text) |
| Trova tutte le corrispondenze | re.findall(pattern, text) |
| Itera le corrispondenze | re.finditer(pattern, text) |
| Corrispondenza all'inizio | re.match(pattern, text) |
| Corrispondenza intera string | re.fullmatch(pattern, text) |
| Sostituisci | re.sub(pattern, repl, text) |
| Dividi | re.split(pattern, text) |
| Compila per riutilizzo | re.compile(pattern) |
| Escape dell'input utente | re.escape(text) |
Capitoli correlati
- Python Strings — metodi string che complementano la regex per compiti testuali più semplici.
- Modify Strings — metodi integrati come
replace()esplit()che evitano la regex quando i pattern sono fissi. - Python File Handling — lettura di file riga per riga per applicare la regex su larga scala.
- Python Try/Except — gestione degli errori che si verificano quando i pattern regex vengono compilati dall'input utente.
- Python Functions — racchiudere la logica regex in funzioni riutilizzabili.