2.36. Grupper och ankare

Ett mönster kan göra mer än att säga ”den här strängen matchar” – det kan plocka isär de matchade delarna och överlämna var och en till applikationen med namn. Parenteser runt en del av ett mönster gör den till en fångstgrupp; matchobjektet exponerar sedan varje grupp som en separat delsträng.

2.36.1. Fångstgrupper

Omslut valfri del av ett mönster med (...) för att fånga det som matchade:

>>> 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'
  • Grupp 0 är alltid hela matchningen.

  • Grupp 1, 2, … är de fångade delsträngarna, numrerade från vänster till höger efter sin öppnande parentes.

  • Att anropa match.group() med ett index efter den sista gruppen ger upphov till IndexError.

Ett vanligt mönster är ”matcha en känd struktur, fånga de variabla delarna som heltal”:

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. Icke-fångande grupper

Parenteser grupperar också ett deluttryck så att en kvantifierare kan tillämpas på hela gruppen. Det är det enda syftet med grupperingen i r'(ab)+' – ”en eller flera av ab”. Att ab dyker upp som grupp 1 är en bieffekt.

För att gruppera utan att fånga, använd (?:...)

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

Icke-fångande grupper håller gruppnumren snygga när ett mönster använder gruppering för struktur men inte bryr sig om att plocka ut varje del.

2.36.3. Ankare

Ankare matchar inte ett tecken – de matchar en position.

  • ^ – början av strängen.

  • $ – slutet av strängen.

Ankare är det som gör att re.match() och re.search() beter sig olika. re.match(p, s) är samma sak som re.search('^' + p, s): det tvingar mönstret att börja vid position 0. Att lägga till $ i slutet av ett mönster gör sedan att mönstret matchar hela strängen och inget annat:

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

^ och $ i MicroPython re betyder alltid början och slutet av hela strängen som skickas till re.search(). Det finns ingen re.MULTILINE-flagga som får dem att matcha vid varje inbäddad radbrytning, och $ matchar inte heller positionen före en avslutande \n – det måste vara det absoluta slutet av indata. För att få radvis beteende, dela först upp indata på radbrytningar och kör mönstret på varje rad.

2.36.4. Teckenuppsättningar

Hakparenteser definierar en explicit uppsättning tecken. Matchningen förbrukar exakt ett tecken från uppsättningen.

  • [abc] – en av a, b, c.

  • [a-z] – ett tecken i intervallet a-z (inklusive).

  • [a-zA-Z0-9] – bokstäver eller siffror. Tre intervall kombinerade.

  • [^abc]inte en av a, b, c. ^ negerar endast när det är det första tecknet inuti hakparenteserna.

Exempel:

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

Det första anropet returnerar i praktiken None eftersom den bokstavliga texten är gemener. MicroPython re har ingen re.IGNORECASE-flagga – för att matcha skiftlägesokänsligt, skriv in båda skiftlägena i uppsättningen:

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

Klassgenvägarna (\d, \s, \w och deras negerade former) kan också användas inuti [...]: [\w-] betyder ”ordtecken eller ett bokstavligt bindestreck.”

2.36.5. Giriga kontra lata kvantifierare

Kvantifierarna *, +, ? och {m,n} är giriga som standard – de matchar så många tecken som resten av mönstret fortfarande tillåter. Ofta är det precis vad som önskas; ibland är det inte det:

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

Den giriga .+ tog hela vägen fram till det sista >. Att lägga till ? gör kvantifieraren lat – den matchar så lite som möjligt:

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

Den lata formen stannar vid det första >. Lata kvantifierare dyker ständigt upp när man extraherar balanserade avgränsare från en sträng.

2.36.6. Bakåtreferenser i substitution

re.sub() kan referera tillbaka till fångade grupper i ersättningssträngen via \1, \2, … Substitutionen skriver om varje matchning med hjälp av de fångade delarna:

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

Varje matchning fångar två tal, och ersättningen byter plats på dem. \g<1> är en alternativ syntax för samma sak – användbart när nästa tecken i ersättningen är en siffra (r'\g<1>0' för att lägga till en bokstavlig nolla efter grupp 1 i stället för att läsa ”grupp 10”).

2.36.7. Vad som inte är tillgängligt

En påminnelse om vad MicroPython re inte stöder, ifall ett mönster från CPython hamnar här och överraskar dig:

  • Framåtblick (?=...) och bakåtblick (?<=...) – inte implementerade.

  • Namngivna grupper (?P<name>...) och namngivna bakåtreferenser (?P=name) – inte implementerade.

  • Flaggkonstanter som re.IGNORECASE, re.MULTILINE, re.DOTALL – respekteras inte. Bygg själv den skiftlägesokänsliga uppsättningen eller dela upp indata i förväg.

  • Metoderna match.groups(), match.span(), match.start() och match.end() är spärrade på en ROM-nivå som inget levererat OpenMV-kort aktiverar. Kod som förlitar sig på dem kör inte på kameran.

Med mönster, grupper och ankare är regex-verktygslådan på kameran tillräckligt liten för att lära sig i ett svep och tillräckligt rik för att göra allt utom kontextkänslig parsning.