2.36. Grupuri și ancore

Un tipar poate face mai mult decât să spună „acest șir corespunde” – poate descompune fragmentele potrivite și poate înmâna fiecare bucată aplicației, după nume. Parantezele din jurul unei părți a unui tipar o transformă într-un grup de captură; obiectul de potrivire expune apoi fiecare grup ca un subșir separat.

2.36.1. Grupuri de captură

Învelește orice parte a unui tipar în (...) pentru a captura ceea ce a fost potrivit:

>>> 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'
  • Grupul 0 este întotdeauna întreaga potrivire.

  • Grupurile 1, 2, … sunt subșirurile capturate, numerotate de la stânga la dreapta după paranteza lor de deschidere.

  • Apelarea metodei match.group() cu un index dincolo de ultimul grup ridică o eroare IndexError.

Un tipar frecvent este „potrivește o structură cunoscută, capturează părțile variabile ca numere întregi”:

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. Grupuri fără captură

Parantezele de asemenea grupează o subexpresie, astfel încât un cuantificator să se poată aplica întregului grup. Acesta este singurul scop al grupării în r'(ab)+' – „unul sau mai multe ab„. Faptul că ab apare ca grupul 1 este un efect secundar.

Pentru a grupa fără a captura, folosește (?:...)

>>> re.search(r'(?:ab)+', 'xababy').group(0)
'abab'

Grupurile fără captură păstrează numerele grupurilor ordonate atunci când un tipar folosește gruparea pentru structură, dar nu îi pasă de extragerea fiecărei bucăți.

2.36.3. Ancore

Ancorele nu potrivesc un caracter – ele potrivesc o poziție.

  • ^ – începutul șirului.

  • $ – sfârșitul șirului.

Ancorele sunt cele care fac ca re.match() și re.search() să se comporte diferit. re.match(p, s) este același lucru cu re.search('^' + p, s): forțează tiparul să înceapă la poziția 0. Adăugarea lui $ la sfârșitul unui tipar face apoi ca tiparul să potrivească întregul șir și nimic altceva:

>>> re.search(r'^\d+$', '12345')
<match num=1>
>>> re.search(r'^\d+$', '12345 ok') is None
True

^ și $ în MicroPython re înseamnă întotdeauna începutul și sfârșitul întregului șir transmis lui re.search(). Nu există un indicator re.MULTILINE care să le facă să potrivească la fiecare caracter de linie nouă încorporat, iar $ nu potrivește nici poziția dinaintea unui \n final – trebuie să fie sfârșitul absolut al intrării. Pentru a obține un comportament pe linie, împarte mai întâi intrarea la liniile noi și rulează tiparul pe fiecare linie.

2.36.4. Seturi de caractere

Parantezele pătrate definesc un set explicit de caractere. Potrivirea consumă exact un caracter din set.

  • [abc] – unul dintre a, b, c.

  • [a-z] – un caracter din intervalul a-z (inclusiv).

  • [a-zA-Z0-9] – litere sau cifre. Trei intervale combinate.

  • [^abc]nu unul dintre a, b, c. ^ negază doar atunci când este primul caracter din interiorul parantezelor.

Exemple:

>>> 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

Primul apel returnează None în practică, deoarece textul literal este cu litere mici. MicroPython re nu are un indicator re.IGNORECASE – pentru a potrivi fără a ține cont de majuscule, scrie ambele variante de litere în set:

>>> re.search(r'[A-Fa-f0-9]{6}', 'colour #1a2b3c rest').group(0)
'1a2b3c'

Scurtăturile de clasă (\d, \s, \w și formele lor negate) pot fi folosite și în interiorul lui [...]: [\w-] înseamnă „caractere de cuvânt sau o liniuță literală.”

2.36.5. Cuantificatori lacomi vs leneși

Cuantificatorii *, +, ? și {m,n} sunt lacomi în mod implicit – potrivesc cât mai multe caractere posibil, atât timp cât restul tiparului încă o permite. Adesea, exact asta se dorește; uneori nu:

>>> re.search(r'<(.+)>', 'a <b> <c> d').group(1)
'b> <c'

.+ lacom a înghițit tot drumul până la ultimul >. Adăugarea lui ? face cuantificatorul leneș – potrivește cât mai puțin posibil:

>>> re.search(r'<(.+?)>', 'a <b> <c> d').group(1)
'b'

Forma leneșă se oprește la primul >. Cuantificatorii leneși apar constant atunci când se extrag delimitatori echilibrați dintr-un șir.

2.36.6. Referințe inverse în substituție

re.sub() se poate referi înapoi la grupurile capturate în șirul de înlocuire prin \1, \2, … Substituția rescrie fiecare potrivire folosind bucățile capturate:

>>> re.sub(r'(\d+)\.(\d+)', r'\2.\1', 'swap 12.34 and 5.6')
'swap 34.12 and 6.5'

Fiecare potrivire capturează două numere, iar înlocuirea le interschimbă. \g<1> este o sintaxă alternativă pentru același lucru – utilă atunci când următorul caracter din înlocuire este o cifră (r'\g<1>0' pentru a adăuga un zero literal la grupul 1, în loc de a citi „grupul 10”).

2.36.7. Ce nu este disponibil

O reamintire a ceea ce MicroPython re nu acceptă, în cazul în care un tipar din CPython ajunge aici și te surprinde:

  • Lookahead (?=...) și lookbehind (?<=...) – neimplementate.

  • Grupuri denumite (?P<name>...) și referințe inverse denumite (?P=name) – neimplementate.

  • Constante de indicatori precum re.IGNORECASE, re.MULTILINE, re.DOTALL – neonorate. Construiește singur setul fără a ține cont de majuscule sau preîmparte singur intrarea.

  • Metodele match.groups(), match.span(), match.start() și match.end() sunt condiționate de un nivel ROM pe care nicio placă OpenMV livrată nu îl activează. Codul care se bazează pe ele nu va rula pe cameră.

Cu tipare, grupuri și ancore, setul de instrumente regex de pe cameră este suficient de mic pentru a fi învățat dintr-o singură ședință și suficient de bogat pentru a face orice, cu excepția analizei sensibile la context.