2.35. Nozioni di base sui pattern¶
Un’espressione regolare è un piccolo linguaggio per descrivere le stringhe in base alla forma anziché al loro contenuto esatto. Usala quando una stringa corrisponde se e solo se segue un pattern che puoi descrivere ma non enumerare: «una sequenza di cifre seguita da un’unità», «una riga che inizia con ERROR e termina con un numero», «una qualsiasi di queste estensioni di file, in qualsiasi ordine, con prefissi v opzionali».
Ricorri a re solo quando un semplice metodo delle stringhe non basta.
str.startswith(),str.endswith()– verifica di un prefisso o suffisso fisso.in– verifica della presenza di una sottostringa fissa.str.split(),str.find(),str.replace()– lavoro con delimitatori fissi.
Ciascuno di questi è più veloce, più facile da leggere e più difficile da sbagliare rispetto alla regex equivalente. Usa le regex quando conta la forma della stringa e non la sottostringa esatta.
2.35.1. Le quattro cose che userai¶
Il modulo re di MicroPython espone quattro elementi:
re.compile()– trasforma una stringa di pattern in un pattern object compilato e riutilizzabile.re.match()– prova il pattern all”inizio di una stringa. Il pattern è ancorato alla posizione 0.re.search()– prova il pattern in qualsiasi punto di una stringa. Restituisce la prima corrispondenza.re.sub()– trova ogni corrispondenza e la sostituisce.
Omissioni di rilievo rispetto a CPython: nessun re.findall, nessun re.finditer, nessun re.split a livello di modulo (i pattern compilati hanno invece un metodo split), nessun re.fullmatch, nessuna costante flag come re.IGNORECASE. Dove su CPython ricorreresti a una di queste, costruisci l’equivalente con re.search() in un ciclo.
2.35.2. Un primo pattern¶
Il pattern r'\d+' corrisponde a una o più cifre:
>>> import re
>>> m = re.search(r'\d+', 'sensor reading 42 ok')
>>> m.group(0)
'42'
Alcune cose da notare:
Il pattern è scritto come raw string (
r'...'), così che il backslash in\darrivi areinvece di essere elaborato come escape di stringa Python. Usa sempre le raw string per i pattern regex.re.search()restituisce un match object in caso di successo eNonein caso di fallimento. Controlla sempre prima di chiamarematch.group().m.group(0)è il testo completo a cui il pattern ha corrisposto. I gruppi 1, 2, … compaiono più avanti, una volta che il pattern contiene parentesi di cattura.
Lo stesso pattern con re.match() restituisce None perché la stringa non inizia con una cifra:
>>> re.match(r'\d+', 'sensor reading 42 ok') is None
True
>>> re.match(r'\d+', '42 readings')
<match num=1>
2.35.3. Le parti di un pattern¶
La maggior parte dei pattern utili è costruita a partire da un piccolo insieme di parti. Quelle che funzionano in MicroPython:
Caratteri letterali – qualsiasi carattere non speciale corrisponde a se stesso. hello corrisponde a hello.
Caratteri speciali – . ^ $ * + ? { } [ ] \ | ( ) hanno tutti i significati riportati di seguito. Per far corrispondere uno di essi letteralmente, usa l’escape con un backslash: \. corrisponde a un punto letterale.
Classi di caratteri – abbreviazioni per insiemi di caratteri comuni:
\d– una cifra qualsiasi0-9\D– un carattere non numerico qualsiasi\s– un carattere di spaziatura qualsiasi (spazio, tabulazione, newline)\S– un carattere non di spaziatura qualsiasi\w– un carattere di «parola» qualsiasi: lettere, cifre, underscore\W– un carattere non di parola qualsiasi.– un carattere qualsiasi tranne il newline
Quantificatori – quante volte deve corrispondere la parte precedente:
*– zero o più (greedy)+– uno o più (greedy)?– zero o uno{n}– esattamente n{m,n}– tra m e n (estremi inclusi)
Combinando: \d{3}-\d{4} corrisponde a tre cifre, un trattino e quattro cifre. \s+ corrisponde a uno o più caratteri di spaziatura. hello.*world corrisponde a hello, qualsiasi cosa (incluso niente) e poi world.
Nota
Greedy significa che il quantificatore consuma quanto più input possibile pur lasciando che il resto del pattern corrisponda. Contro hello x world y world, il .* in hello.*world corrisponde alla sequenza più lunga che lascia comunque un world alla fine: cattura x world y, non il più breve x. Lo stesso vale per + e per la forma con intervallo {m,n}: il motore prende la corrispondenza più lunga possibile e poi torna indietro solo se il resto del pattern fallisce.
2.35.4. Sostituzione¶
re.sub() trova ogni corrispondenza e la sostituisce con una stringa. La sostituzione può riferirsi ai gruppi catturati tramite \1, \2, … (trattato più avanti insieme al resto della sintassi dei gruppi). Senza gruppi, re.sub è una semplice operazione di cerca-e-sostituisci su una regex:
>>> re.sub(r'\s+', ' ', 'too many spaces')
'too many spaces'
>>> re.sub(r'\d+', 'N', 'log 12, log 345, log 6')
'log N, log N, log N'
Il terzo argomento è la stringa su cui operare; il risultato è una nuova stringa con ogni corrispondenza sostituita.
2.35.5. Suddivisione – solo su un pattern compilato¶
Non esiste re.split a livello di modulo. Per suddividere su una regex, compila prima il pattern e chiama il suo metodo split
>>> sep = re.compile(r'\s*,\s*')
>>> sep.split('a , b,c , d')
['a', 'b', 'c', 'd']
Il secondo argomento opzionale limita il numero di suddivisioni:
>>> sep.split('a, b, c, d', 2)
['a', 'b', 'c, d']
2.35.6. Compilare per il riutilizzo¶
Se lo stesso pattern viene eseguito molte volte – dentro un ciclo o in una funzione critica per le prestazioni – compilalo una volta e riutilizza l’oggetto compilato:
digit_run = re.compile(r'\d+')
def first_number(line):
m = digit_run.search(line)
return int(m.group(0)) if m else None
Chiamare pattern.match() e pattern.search() su un oggetto compilato equivale alle funzioni a livello di modulo, ma evita il costo della ricompilazione a ogni chiamata.
2.35.7. Pattern che non corrispondono a nulla¶
Tre pattern in particolare colgono di sorpresa gli sviluppatori:
.*corrisponde alla stringa vuota.re.search(r'.*', s).group(0)restituisce''con qualsiasi input.Un pattern con un carattere speciale senza escape è un errore di sintassi.
re.compile(r'cost: $5')sollevaValueErrorperché$significa «fine della stringa». Usar'cost: \$5'.Il punto
.non corrisponde a un newline. Per corrispondere attraverso i newline, scrivi il pattern in modo da gestirli esplicitamente con[\s\S]oppure passa una riga alla volta.
Con queste parti un pattern può corrispondere a quasi ogni porzione di testo a forma fissa. Estrarre i dati strutturati dalla corrispondenza richiede i gruppi di cattura.