2.36. Groepen en ankers

Een patroon kan meer doen dan zeggen “deze string komt overeen” – het kan de overeenkomende stukken uit elkaar halen en elk stuk op naam aan de applicatie doorgeven. Haakjes rond een deel van een patroon maken er een capturing group van; het match-object stelt vervolgens elke groep beschikbaar als een aparte substring.

2.36.1. Capturing groups

Plaats een willekeurig deel van een patroon tussen (...) om vast te leggen waarmee het overeenkwam:

>>> 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'
  • Groep 0 is altijd de volledige overeenkomst.

  • Groepen 1, 2, … zijn de vastgelegde substrings, van links naar rechts genummerd op basis van hun openende haakje.

  • Het aanroepen van match.group() met een index voorbij de laatste groep veroorzaakt een IndexError.

Een veelvoorkomend patroon is “match een bekende structuur, leg de variabele delen vast als ints”:

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. Non-capturing groups

Haakjes groeperen ook een subexpressie, zodat een kwantor op de hele groep kan worden toegepast. Dat is het enige doel van groeperen in r'(ab)+' – “een of meer keer ab“. Dat ab als groep 1 verschijnt, is een neveneffect.

Gebruik (?:...) om te groeperen zonder vast te leggen:

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

Non-capturing groups houden de groepsnummers overzichtelijk wanneer een patroon groepering gebruikt voor structuur, maar er niet om geeft elk stuk eruit te halen.

2.36.3. Ankers

Ankers komen niet overeen met een teken – ze komen overeen met een positie.

  • ^ – begin van de string.

  • $ – einde van de string.

Ankers zorgen ervoor dat re.match() en re.search() zich verschillend gedragen. re.match(p, s) is hetzelfde als re.search('^' + p, s): het dwingt het patroon om op positie 0 te beginnen. Door $ aan het einde van een patroon toe te voegen, komt het patroon vervolgens overeen met de gehele string en niets anders:

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

^ en $ in MicroPython re betekenen altijd het begin en einde van de gehele string die aan re.search() wordt doorgegeven. Er is geen re.MULTILINE-vlag om ze bij elke ingesloten newline te laten overeenkomen, en $ komt ook niet overeen met de positie vlak voor een afsluitende \n – het moet het absolute einde van de invoer zijn. Splits de invoer eerst op newlines en voer het patroon op elke regel uit om gedrag per regel te krijgen.

2.36.4. Tekensets

Vierkante haken definiëren een expliciete set tekens. De overeenkomst verbruikt precies één teken uit de set.

  • [abc] – een van a, b, c.

  • [a-z] – één teken in het bereik a-z (inclusief).

  • [a-zA-Z0-9] – letters of cijfers. Drie bereiken gecombineerd.

  • [^abc]geen van a, b, c. De ^ negeert alleen wanneer het het eerste teken binnen de haken is.

Voorbeelden:

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

De eerste aanroep retourneert in de praktijk None omdat de letterlijke tekst in kleine letters staat. MicroPython re heeft geen re.IGNORECASE-vlag – schrijf beide hoofdletter-/kleineletterversies in de set om hoofdletterongevoelig te matchen:

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

De klasse-afkortingen (\d, \s, \w en hun genegeerde vormen) kunnen ook binnen [...] worden gebruikt: [\w-] betekent “woordtekens of een letterlijk koppelteken.”

2.36.5. Greedy versus lazy kwantoren

De kwantoren *, +, ? en {m,n} zijn standaard greedy – ze matchen zoveel tekens als de rest van het patroon nog toelaat. Vaak is dat precies wat je wilt; soms niet:

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

De greedy .+ greep helemaal door tot aan de laatste >. Door ? toe te voegen wordt de kwantor lazy – hij matcht zo weinig mogelijk:

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

De lazy vorm stopt bij de eerste >. Lazy kwantoren komen voortdurend van pas bij het uit een string halen van uitgebalanceerde scheidingstekens.

2.36.6. Backreferences in vervanging

re.sub() kan in de vervangingsstring terugverwijzen naar vastgelegde groepen via \1, \2, … De vervanging herschrijft elke overeenkomst met behulp van de vastgelegde stukken:

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

Elke overeenkomst legt twee getallen vast, en de vervanging verwisselt ze. \g<1> is een alternatieve syntaxis voor hetzelfde – handig wanneer het volgende teken in de vervanging een cijfer is (r'\g<1>0' om een letterlijke nul aan groep 1 toe te voegen in plaats van “groep 10” te lezen).

2.36.7. Wat niet beschikbaar is

Een herinnering aan wat de MicroPython re niet ondersteunt, voor het geval een patroon uit CPython hier belandt en je verrast:

  • Lookahead (?=...) en lookbehind (?<=...) – niet geïmplementeerd.

  • Benoemde groepen (?P<name>...) en benoemde backreferences (?P=name) – niet geïmplementeerd.

  • Vlagconstanten zoals re.IGNORECASE, re.MULTILINE, re.DOTALL – worden niet gehonoreerd. Bouw zelf de hoofdletterongevoelige set of splits de invoer vooraf.

  • De methoden match.groups(), match.span(), match.start() en match.end() zijn afhankelijk van een ROM-niveau dat geen enkel geleverd OpenMV-board inschakelt. Code die hierop steunt, werkt niet op de cam.

Met patronen, groepen en ankers is de regex-toolset op de cam klein genoeg om in één keer te leren en rijk genoeg om alles te doen behalve context-gevoelig parseren.