2.36. Gruppi e ancore#
Un pattern può fare di più che dire «questa stringa corrisponde»: può scomporre le parti trovate e passarne ognuna all’applicazione tramite un nome. Le parentesi attorno a una porzione di un pattern la rendono un gruppo di cattura; il match object espone quindi ogni gruppo come una sottostringa separata.
2.36.1. Gruppi di cattura#
Racchiudi una qualsiasi porzione di un pattern tra (...) per catturare ciò che ha trovato:
>>> import re
>>> m = re.search(r'temp (\d+) at (\d+)s', 'temp 42 at 137s ok')
>>> m.group(0)
'temp 42 at 137s'
>>> m.group(1)
'42'
>>> m.group(2)
'137'
Il gruppo 0 è sempre l’intera corrispondenza.
I gruppi 1, 2, … sono le sottostringhe catturate, numerate da sinistra a destra in base alla loro parentesi di apertura.
Chiamare
match.group()con un indice oltre l’ultimo gruppo sollevaIndexError.
Un pattern comune è «trova una struttura nota, cattura le parti variabili come interi»:
def parse_temp(line):
m = re.search(r'temp (\d+) at (\d+)s', line)
if not m:
return None
return int(m.group(1)), int(m.group(2))
2.36.2. Gruppi senza cattura#
Le parentesi raggruppano anche una sotto-espressione, in modo che un quantificatore possa applicarsi all’intero gruppo. È questo l’unico scopo del raggruppamento in r'(ab)+': «uno o più ab«. Il fatto che ab compaia come gruppo 1 è un effetto collaterale.
Per raggruppare senza catturare, usa (?:...)
>>> re.search(r'(?:ab)+', 'xababy').group(0)
'abab'
I gruppi senza cattura mantengono ordinata la numerazione dei gruppi quando un pattern usa il raggruppamento per la struttura ma non ha bisogno di estrarre ogni singola parte.
2.36.3. Ancore#
Le ancore non corrispondono a un carattere: corrispondono a una posizione.
^– inizio della stringa.$– fine della stringa.
Le ancore sono ciò che fa comportare diversamente re.match() e re.search(). re.match(p, s) equivale a re.search('^' + p, s): forza il pattern a iniziare alla posizione 0. Aggiungere $ alla fine di un pattern fa quindi sì che il pattern corrisponda all”intera stringa e a nient’altro:
>>> re.search(r'^\d+$', '12345')
<match num=1>
>>> re.search(r'^\d+$', '12345 ok') is None
True
In MicroPython re, ^ e $ indicano sempre l’inizio e la fine dell”intera stringa passata a re.search(). Non esiste un flag re.MULTILINE per farli corrispondere a ogni newline interno, e $ non corrisponde nemmeno alla posizione prima di un \n finale: deve essere la fine assoluta dell’input. Per ottenere un comportamento per riga, dividi prima l’input sui newline ed esegui il pattern su ogni riga.
2.36.4. Insiemi di caratteri#
Le parentesi quadre definiscono un insieme esplicito di caratteri. La corrispondenza consuma esattamente un carattere dell’insieme.
[abc]– uno traa,b,c.[a-z]– un carattere nell’intervalloa-z(estremi inclusi).[a-zA-Z0-9]– lettere o cifre. Tre intervalli combinati.[^abc]– non uno traa,b,c. Il^nega solo quando è il primo carattere all’interno delle parentesi.
Esempi:
>>> re.search(r'[A-F0-9]{6}', 'colour #1a2b3c rest').group(0)
'1A2B3C'
>>> re.search(r'[A-F0-9]{6}', 'colour #1a2b3c rest') is None
True
In pratica la prima chiamata restituisce None perché il testo letterale è in minuscolo. MicroPython re non ha alcun flag re.IGNORECASE: per una corrispondenza che ignori le maiuscole/minuscole, inserisci entrambe le forme nell’insieme:
>>> re.search(r'[A-Fa-f0-9]{6}', 'colour #1a2b3c rest').group(0)
'1a2b3c'
Le scorciatoie di classe (\d, \s, \w e le loro forme negate) possono essere usate anche dentro [...]: [\w-] significa «caratteri di parola o un trattino letterale».
2.36.5. Quantificatori greedy e lazy#
I quantificatori *, +, ? e {m,n} sono greedy per impostazione predefinita: corrispondono a quanti più caratteri possibile pur lasciando che il resto del pattern continui a corrispondere. Spesso è esattamente ciò che si vuole; a volte no:
>>> re.search(r'<(.+)>', 'a <b> <c> d').group(1)
'b> <c'
Il .+ greedy ha catturato fino all’ultimo >. Aggiungere ? rende il quantificatore lazy: corrisponde a quanto meno possibile:
>>> re.search(r'<(.+?)>', 'a <b> <c> d').group(1)
'b'
La forma lazy si ferma al primo >. I quantificatori lazy ricorrono di continuo quando si estraggono delimitatori bilanciati da una stringa.
2.36.6. Backreference nella sostituzione#
re.sub() può riferirsi ai gruppi catturati nella stringa di sostituzione tramite \1, \2, … La sostituzione riscrive ogni corrispondenza usando le parti catturate:
>>> re.sub(r'(\d+)\.(\d+)', r'\2.\1', 'swap 12.34 and 5.6')
'swap 34.12 and 6.5'
Ogni corrispondenza cattura due numeri e la sostituzione li scambia. \g<1> è una sintassi alternativa per la stessa cosa, utile quando il carattere successivo nella sostituzione è una cifra (r'\g<1>0' per aggiungere uno zero letterale al gruppo 1 invece di leggere «gruppo 10»).
2.36.7. Cosa non è disponibile#
Un promemoria di ciò che MicroPython re non supporta, nel caso un pattern proveniente da CPython finisca qui e ti sorprenda:
Lookahead
(?=...)e lookbehind(?<=...)– non implementati.Gruppi con nome
(?P<name>...)e backreference con nome(?P=name)– non implementati.Costanti flag come
re.IGNORECASE,re.MULTILINE,re.DOTALL– non rispettate. Costruisci tu stesso l’insieme insensibile alle maiuscole o pre-suddividi l’input.I metodi
match.groups(),match.span(),match.start()ematch.end()sono vincolati a un livello ROM che nessuna scheda OpenMV in commercio abilita. Il codice che vi fa affidamento non funzionerà sulla cam.
Con pattern, gruppi e ancore, il set di strumenti per le regex sulla cam è abbastanza piccolo da imparare in una sola seduta e abbastanza ricco da fare tutto, tranne il parsing sensibile al contesto.