2.35. Patroonbasis

Een reguliere expressie is een kleine taal om strings te beschrijven op basis van hun vorm in plaats van hun exacte inhoud. Gebruik deze wanneer een string overeenkomt dan en slechts dan als hij een patroon volgt dat je kunt beschrijven maar niet kunt opsommen – “een reeks cijfers gevolgd door een eenheid”, “een regel die begint met ERROR en eindigt met een getal”, “een van deze bestandsextensies, in willekeurige volgorde, met optionele v-voorvoegsels”.

Grijp alleen naar re wanneer een gewone stringmethode niet voldoet.

Elk daarvan is sneller, makkelijker te lezen en minder foutgevoelig dan de equivalente regex. Gebruik regex wanneer de vorm van de string van belang is en de exacte substring niet.

2.35.1. De vier dingen die je gaat gebruiken

De MicroPython re-module biedt vier dingen:

  • re.compile() – zet een patroonstring om in een gecompileerd patroonobject dat je kunt hergebruiken.

  • re.match() – probeer het patroon aan het begin van een string. Het patroon is verankerd op positie 0.

  • re.search() – probeer het patroon ergens in een string. Retourneert de eerste overeenkomst.

  • re.sub() – vind elke overeenkomst en vervang deze.

Opvallende weglatingen ten opzichte van CPython: geen re.findall, geen re.finditer, geen re.split op moduleniveau (gecompileerde patronen hebben in plaats daarvan een split-methode), geen re.fullmatch, geen vlagconstanten zoals re.IGNORECASE. Waar je op CPython naar een van die zou grijpen, bouw je het equivalent uit re.search() in een lus.

2.35.2. Een eerste patroon

Het patroon r'\d+' komt overeen met een of meer cijfers:

>>> import re
>>> m = re.search(r'\d+', 'sensor reading 42 ok')
>>> m.group(0)
'42'

Een paar dingen om op te merken:

  • Het patroon wordt geschreven als een raw string (r'...') zodat de backslash in \d re bereikt in plaats van als een Python-string-escape te worden verwerkt. Gebruik altijd raw strings voor regex-patronen.

  • re.search() retourneert bij succes een match-object en bij mislukking None. Controleer dit altijd voordat je match.group() aanroept.

  • m.group(0) is de volledige tekst waarmee het patroon overeenkwam. Groep 1, 2, … verschijnen later, zodra het patroon capturing-haakjes bevat.

Hetzelfde patroon met re.match() retourneert None omdat de string niet met een cijfer begint

>>> re.match(r'\d+', 'sensor reading 42 ok') is None
True
>>> re.match(r'\d+', '42 readings')
<match num=1>

2.35.3. De onderdelen van een patroon

De meeste nuttige patronen zijn opgebouwd uit een kleine set onderdelen. Degene die in MicroPython werken:

Letterlijke tekens – elk teken dat niet speciaal is, komt met zichzelf overeen. hello komt overeen met hello.

Speciale tekens. ^ $ * + ? { } [ ] \ | ( ) hebben allemaal de hieronder beschreven betekenissen. Om er een letterlijk te matchen, escape je het met een backslash: \. komt overeen met een letterlijke punt.

Tekenklassen – afkortingen voor veelvoorkomende tekensets:

  • \d – elk cijfer 0-9

  • \D – elk niet-cijfer

  • \s – elk witruimteteken (spatie, tab, newline)

  • \S – elk niet-witruimteteken

  • \w – elk “woord”-teken: letters, cijfers, underscore

  • \W – elk niet-woordteken

  • . – elk teken behalve newline

Kwantoren – hoe vaak het voorgaande onderdeel moet overeenkomen:

  • * – nul of meer (greedy)

  • + – een of meer (greedy)

  • ? – nul of een

  • {n} – precies n

  • {m,n} – tussen m en n (inclusief)

Combineren: \d{3}-\d{4} komt overeen met drie cijfers, een streepje, vier cijfers. \s+ komt overeen met een of meer witruimtetekens. hello.*world komt overeen met hello, gevolgd door wat dan ook (inclusief niets), en dan world.

Notitie

Greedy betekent dat de kwantor zoveel mogelijk van de invoer verbruikt terwijl de rest van het patroon nog steeds kan overeenkomen. Tegen hello x world y world matcht de .* in hello.*world de langste reeks die nog steeds een world aan het einde overlaat – hij legt x world y vast, niet de kortere x. Hetzelfde geldt voor + en de bereikvorm {m,n}: de engine neemt de langst mogelijke overeenkomst en wijkt pas terug als de rest van het patroon mislukt.

2.35.4. Vervanging

re.sub() vindt elke overeenkomst en vervangt deze door een string. De vervanging kan via \1, \2, … verwijzen naar vastgelegde groepen (verderop behandeld met de rest van de groepssyntaxis). Zonder groepen is re.sub een rechttoe rechtaan zoek-en-vervang op een 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'

Het derde argument is de string waarop bewerkt moet worden; het resultaat is een nieuwe string met elke overeenkomst vervangen.

2.35.5. Splitsen – alleen op een gecompileerd patroon

Er is geen re.split op moduleniveau. Compileer eerst het patroon en roep de bijbehorende split-methode aan om op een regex te splitsen:

>>> sep = re.compile(r'\s*,\s*')
>>> sep.split('a , b,c ,  d')
['a', 'b', 'c', 'd']

Het optionele tweede argument beperkt het aantal splitsingen:

>>> sep.split('a, b, c, d', 2)
['a', 'b', 'c, d']

2.35.6. Compileren voor hergebruik

Als hetzelfde patroon vele keren wordt uitgevoerd – binnen een lus of in een veelgebruikte functie – compileer het dan één keer en hergebruik het gecompileerde object:

digit_run = re.compile(r'\d+')

def first_number(line):
    m = digit_run.search(line)
    return int(m.group(0)) if m else None

Het aanroepen van pattern.match() en pattern.search() op een gecompileerd object is hetzelfde als de functies op moduleniveau, maar slaat de hercompileerkosten bij elke aanroep over.

2.35.7. Patronen die nergens mee overeenkomen

Vooral drie patronen brengen ontwikkelaars in verwarring:

  • .* komt overeen met de lege string. re.search(r'.*', s).group(0) retourneert '' bij elke invoer.

  • Een patroon met een niet-ge-escaped speciaal teken is een syntaxisfout. re.compile(r'cost: $5') veroorzaakt een ValueError omdat $ “einde van string” betekent. Gebruik r'cost: \$5'.

  • De punt . komt niet overeen met een newline. Om over newlines heen te matchen, schrijf je het patroon zo dat het ze expliciet afhandelt met [\s\S] of voer je één regel tegelijk aan.

Met deze onderdelen kan een patroon vrijwel elk vastevorm-stuk tekst matchen. Het terughalen van gestructureerde data uit de overeenkomst vereist capturing groups.