Reguliere expressies zijn een krachtige taal voor het matchen van tekst patronen. Deze pagina geeft een basis inleiding in reguliere expressies zelf voldoende voor onze Python oefeningen en laat zien hoe reguliere expressies werken in Python. De Python “re” module biedt ondersteuning voor reguliere expressies.

In Python wordt een zoekterm voor een reguliere expressie meestal geschreven als:

 match = re.search(pat, str)

De methode re.search() neemt een regulier expressiepatroon en een tekenreeks en zoekt naar dat patroon binnen de tekenreeks. Indien de zoekopdracht succesvol is, retourneert search() een match object of Geen anders. Daarom wordt de zoekopdracht gewoonlijk onmiddellijk gevolgd door een if-statement om te testen of de zoekopdracht is geslaagd, zoals in het volgende voorbeeld waarin wordt gezocht naar het patroon ‘word:’ gevolgd door een 3-letterig woord (details hieronder):

str = 'an example word:cat!!'match = re.search(r'word:\w\w\w', str)# If-statement after search() tests if it succeededif match: print 'found', match.group() ## 'found word:cat'else: print 'did not find'

De code match = re.search(pat, str) slaat het zoekresultaat op in een variabele met de naam “match”. Dan test het if-statement de match — indien true is de zoekopdracht geslaagd en match.group() is de overeenkomende tekst (b.v. ‘word:cat’). Anders, als de match false is (None om specifieker te zijn), dan is het zoeken niet gelukt, en is er geen overeenkomende tekst.

De ‘r’ aan het begin van de pattern string geeft een python “raw” string aan die door backslashes gaat zonder verandering, wat erg handig is voor reguliere expressies (Java heeft deze functie hard nodig!). Ik raad je aan om patroonreeksen altijd met de ‘r’ te schrijven, gewoon voor de gewoonte.

Basispatronen

De kracht van reguliere expressies is dat ze patronen kunnen specificeren, niet alleen vaste tekens. Hier zijn de meest elementaire patronen die overeenkomen met enkele karakters:

  • a, X, 9, < — gewone karakters komen gewoon exact met zichzelf overeen. De meta-tekens die niet met zichzelf overeenkomen omdat ze een speciale betekenis hebben zijn: . ^ $ * + ? (details hieronder)
  • . (een punt) — komt overeen met elk afzonderlijk teken behalve de nieuwe regel “\n”
  • \w — (kleine letter w) komt overeen met een “woord”-teken: een letter of cijfer of een onderstreepje . Merk op dat, hoewel “woord” het geheugensteuntje hiervoor is, het alleen overeenkomt met een enkel woord teken, niet met een heel woord. \W (hoofdletter W) komt overeen met elk niet-woord teken.
  • \b — grens tussen woord en niet-woord
  • \s — (kleine letters s) komt overeen met een enkel witruimteteken — spatie, nieuwe regel, return, tab, vorm . \S (hoofdletter S) komt overeen met elk teken dat geen spatie is.
  • \t, \n, \r — tab, newline, return
  • \d — decimaalteken (sommige oudere regexprogramma’s ondersteunen alleen \d, maar ze ondersteunen allemaal \w en \s)
  • ^ = begin, $ = einde — komt overeen met het begin of einde van de tekenreeks
  • \ — verbiedt de “speciaalheid” van een teken. Dus, bijvoorbeeld, gebruik \ om een punt te gebruiken of \ om een schuine streep te gebruiken. Als u niet zeker weet of een teken een speciale betekenis heeft, zoals ‘@’, kunt u er een schuine streep voor zetten, \@, om er zeker van te zijn dat het gewoon als een teken wordt behandeld.

Basic Examples

Joke: hoe noem je een varken met drie ogen? piiig!

De basisregels van het zoeken met reguliere expressie naar een patroon in een tekenreeks zijn:

  • Het zoeken gaat door de tekenreeks van begin tot eind, en stopt bij de eerste gevonden overeenkomst
  • Het hele patroon moet worden gematcht, maar niet de hele tekenreeks
  • Als match = re.search(pat, str) succesvol is, is match niet Geen en in het bijzonder match.group() is de overeenkomende tekst
 ## Search for pattern 'iii' in string 'piiig'. ## All of the pattern must match, but it may appear anywhere. ## On success, match.group() is matched text. match = re.search(r'iii', 'piiig') # found, match.group() == "iii" match = re.search(r'igs', 'piiig') # not found, match == None ## . = any char but \n match = re.search(r'..g', 'piiig') # found, match.group() == "iig" ## \d = digit char, \w = word char match = re.search(r'\d\d\d', 'p123g') # found, match.group() == "123" match = re.search(r'\w\w\w', '@@abcd!!') # found, match.group() == "abc"

Herhaling

Het wordt interessanter als u + en * gebruikt om herhaling in het patroon aan te geven

  • + — 1 of meer keren dat het patroon links ervan voorkomt, b.v. ‘i+’ = een of meer i’s
  • * — 0 of meer keren dat het patroon links ervan voorkomt
  • ? — overeenkomen met 0 of 1 voorkomen van het patroon links ervan

Linksste & Grootste

Eerst wordt de meest linkse overeenkomst voor het patroon gevonden, en vervolgens wordt geprobeerd zoveel mogelijk van de string op te gebruiken — d.w.z. + en * gaan zo ver mogelijk (de + en * worden “greedy” genoemd).

Herhalingsvoorbeelden

 ## i+ = one or more i's, as many as possible. match = re.search(r'pi+', 'piiig') # found, match.group() == "piii" ## Finds the first/leftmost solution, and within it drives the + ## as far as possible (aka 'leftmost and largest'). ## In this example, note that it does not get to the second set of i's. match = re.search(r'i+', 'piigiiii') # found, match.group() == "ii" ## \s* = zero or more whitespace chars ## Here look for 3 digits, possibly separated by whitespace. match = re.search(r'\d\s*\d\s*\d', 'xx1 2 3xx') # found, match.group() == "1 2 3" match = re.search(r'\d\s*\d\s*\d', 'xx12 3xx') # found, match.group() == "12 3" match = re.search(r'\d\s*\d\s*\d', 'xx123xx') # found, match.group() == "123" ## ^ = matches the start of string, so this fails: match = re.search(r'^b\w+', 'foobar') # not found, match == None ## but without the ^ it succeeds: match = re.search(r'b\w+', 'foobar') # found, match.group() == "bar"

E-mails Voorbeeld

Voorstel dat u het emailadres wilt vinden in de string ‘xyz [email protected] purple monkey’. We zullen dit gebruiken als een lopend voorbeeld om meer reguliere expressie mogelijkheden te demonstreren. Hier is een poging met het patroon r’\w+@\w+’:

 str = 'purple [email protected] monkey dishwasher' match = re.search(r'\w+@\w+', str) if match: print match.group() ## 'b@google'

Het zoeken levert in dit geval niet het hele email adres op, omdat de \w niet overeenkomt met de ‘-‘ of ‘.’ in het adres. We zullen dit oplossen met de reguliere expressie hieronder.

Haakjes

Haakjes kunnen worden gebruikt om een reeks tekens aan te geven, dus komt overeen met ‘a’ of ‘b’ of ‘c’. De codes \w, \s etc. werken ook binnen vierkante haken, met als enige uitzondering dat punt (.) gewoon een letterlijke punt betekent. Voor het emailprobleem zijn de vierkante haken een gemakkelijke manier om ‘.’ en ‘-‘ toe te voegen aan de reeks tekens die rond de @ kunnen staan met het patroon r’+@+’ om het hele emailadres te krijgen:

 match = re.search(r'+@+', str) if match: print match.group() ## '[email protected]'

(Meer mogelijkheden met vierkante haken) Je kunt ook een liggend streepje gebruiken om een bereik aan te geven, zodat het overeenkomt met alle kleine letters. Om een streepje te gebruiken zonder een bereik aan te geven, zet je het streepje als laatste, bijv. Een opstaand streepje (^) aan het begin van een reeks met vierkante haken keert deze om, dus komt overeen met elk teken behalve ‘a’ of ‘b’.

Groepsextractie

Met de “groep”-functie van een reguliere expressie kunt u delen van de overeenkomende tekst eruit pikken. Stel dat we voor het e-mailprobleem de gebruikersnaam en host apart willen extraheren. Om dit te doen, voeg haakjes ( ) toe rond de gebruikersnaam en host in het patroon, zoals dit: r'(+)@(+)’. In dit geval veranderen de haakjes niet wat het patroon zal overeenkomen, in plaats daarvan maken ze logische “groepen” binnen de overeenkomende tekst. Bij een succesvolle zoekopdracht, match.group(1) is de overeenkomende tekst die overeenkomt met de 1ste linker haakjes, en match.group(2) is de tekst die overeenkomt met de 2de linker haakjes. De gewone match.group() is nog steeds de hele match tekst zoals gebruikelijk.

 str = 'purple [email protected] monkey dishwasher' match = re.search(r'(+)@(+)', str) if match: print match.group() ## '[email protected]' (the whole match) print match.group(1) ## 'alice-b' (the username, group 1) print match.group(2) ## 'google.com' (the host, group 2)

Een gebruikelijke werkwijze met reguliere expressies is dat je een patroon schrijft voor datgene waar je naar zoekt, en haakjesgroepen toevoegt om de delen eruit te halen die je wilt.

findall

findall() is waarschijnlijk de meest krachtige functie in de re module. Hierboven gebruikten we re.search() om de eerste overeenkomst te vinden voor een patroon. findall() vindt *alle* overeenkomsten en geeft ze terug als een lijst van strings, waarbij elke string een overeenkomst weergeeft.

 ## Suppose we have a text with many email addresses str = 'purple [email protected], blah monkey [email protected] blah dishwasher' ## Here re.findall() returns a list of all the found email strings emails = re.findall(r'+@+', str) ## for email in emails: # do something with each found email string print email

findall With Files

Voor bestanden is het misschien uw gewoonte om een lus te schrijven om de regels van het bestand te doorlopen, en u zou dan findall() op elke regel kunnen aanroepen. In plaats daarvan, laat je findall() de iteratie voor je doen — veel beter! Voer gewoon de hele bestandstekst in findall() en laat het een lijst teruggeven van alle overeenkomsten in een enkele stap (onthoud dat f.read() de hele tekst van een bestand in een enkele string teruggeeft):

 # Open file f = open('test.txt', 'r') # Feed the file text into findall(); it returns a list of all the found strings strings = re.findall(r'some pattern', f.read())

findall and Groups

Het parenthesis ( ) groep mechanisme kan gecombineerd worden met findall(). Als het patroon 2 of meer haakjes groepen bevat, dan in plaats van een lijst van strings, geeft findall() een lijst van *tupels* terug. Elke tupel vertegenwoordigt een overeenkomst van het patroon, en binnen de tupel is de groep(1), groep(2) … data. Dus als 2 haakjes groepen zijn toegevoegd aan het email patroon, dan geeft findall() een lijst van tupels terug, elke lengte 2 met daarin de gebruikersnaam en host, bijv. (‘alice’, ‘google.com’).

 str = 'purple [email protected], blah monkey [email protected] blah dishwasher' tuples = re.findall(r'(+)@(+)', str) print tuples ## for tuple in tuples: print tuple ## username print tuple ## host

Als je eenmaal de lijst met tupels hebt, kun je er een lus overheen maken om berekeningen uit te voeren voor elke tupel. Als het patroon geen haakjes bevat, dan geeft findall() een lijst van gevonden strings terug, zoals in eerdere voorbeelden. Als het patroon een enkele set haakjes bevat, dan geeft findall() een lijst van strings terug die overeenkomen met die enkele groep. (Obscure optionele functie: Soms heb je paren ( ) groeperingen in het patroon, maar die je niet wilt extraheren. In dat geval, schrijf de parens met een ?: aan het begin, bijvoorbeeld (?: ) en die linker paren zullen niet tellen als een groep resultaat.)

RE Workflow and Debug

Reguliere expressie patronen verpakken veel betekenis in slechts een paar karakters , maar ze zijn zo dicht, dat je veel tijd kunt besteden aan het debuggen van je patronen. Stel je runtime zo in dat je een patroon kunt uitvoeren en eenvoudig kunt afdrukken waar het mee overeenkomt, bijvoorbeeld door het op een kleine testtekst uit te voeren en het resultaat van findall() af te drukken. Als het patroon met niets overeenkomt, probeer dan het patroon af te zwakken, door delen ervan te verwijderen, zodat je te veel overeenkomsten krijgt. Als het met niets overeenkomt, kun je geen vooruitgang boeken omdat er niets concreets is om naar te kijken. Als het teveel overeenkomt, kun je werken aan het aanscherpen van het patroon, zodat je precies krijgt wat je wilt.

Opties

De re functies nemen opties om het gedrag van het patroon aan te passen. De optie vlag wordt toegevoegd als een extra argument aan de search() of findall() enz., b.v. re.search(pat, str, re.IGNORECASE).

  • IGNORECASE — negeer hoofdletters/kleine letters verschillen voor matching, dus ‘a’ komt overeen met zowel ‘a’ als ‘A’.
  • DOTALL — laat punt (.) overeenkomen met newline — normaal komt het overeen met alles behalve newline. Dit kan je in de war brengen — je denkt dat .* met alles overeenkomt, maar standaard gaat het niet verder dan het einde van een regel. Merk op dat ^s (witregels) ook nieuwe regels bevat, dus als je een reeks witregels wilt gebruiken die ook een nieuwe regel kan bevatten, kun je gewoon ^s*
  • MULTILINE — Binnen een tekenreeks die uit veel regels bestaat, kun je ^ en $ gebruiken om het begin en einde van elke regel weer te geven. Normaal zou ^/$ alleen overeenkomen met het begin en eind van de hele string.

Greedy vs. Non-Greedy (optioneel)

Dit is een optioneel gedeelte dat een meer geavanceerde reguliere expressietechniek laat zien die niet nodig is voor de oefeningen.

Stel dat je tekst hebt met tags erin: <b>foo</b> en <i>zo</i>

Vooropgesteld dat u elke tag probeert te matchen met het patroon ‘(<.*>)’ — waar komt het eerst mee overeen?

Het resultaat is een beetje verrassend, maar het hebzuchtige aspect van de .* zorgt ervoor dat het de hele ‘<b>foo</b> en <i>zo</i>’ matcht als één grote overeenkomst. Het probleem is dat de .* zo ver gaat als het kan, in plaats van te stoppen bij de eerste > (aka het is “greedy”).

Er is een uitbreiding op reguliere expressies waar je een ? toevoegt aan het eind, zoals .*? of .+?, waardoor ze niet meer greedy zijn. Nu stoppen ze zo snel als ze kunnen. Dus het patroon ‘(<.*?>)’ krijgt alleen ‘<b>’ als eerste overeenkomst, en ‘</b>’ als tweede overeenkomst, en zo verder met het krijgen van elk <..> paar op zijn beurt. De stijl is typisch dat je een .*? gebruikt, en dan direct rechts daarvan zoekt naar een concrete marker (> in dit geval) die het einde van de .*? run forceert.

De *? extensie is ontstaan in Perl, en reguliere expressies die Perl’s extensies bevatten staan bekend als Perl Compatible Regular Expressions — pcre. Python bevat pcre ondersteuning. Veel command line utils etc. hebben een vlag waarmee ze pcre patronen accepteren.

Een oudere maar veel gebruikte techniek om dit idee van “al deze karakters behalve stoppen bij X” te coderen gebruikt de vierkante haken stijl. Voor het bovenstaande zou je het patroon kunnen schrijven, maar in plaats van .* om alle tekens te krijgen, gebruik je * dat alle tekens overslaat die niet > zijn (de leidende ^ “keert” de vierkante haakjes reeks om, zodat het overeenkomt met elk teken dat niet tussen de haakjes staat).

Substitutie (optioneel)

De re.sub(pat, replacement, str) functie zoekt naar alle instanties van het patroon in de gegeven string, en vervangt ze. De vervangende string kan ‘\1’, ‘\2’ bevatten, die verwijzen naar de tekst uit groep(1), groep(2), enzovoort uit de oorspronkelijke overeenkomende tekst.

Hier volgt een voorbeeld dat alle e-mailadressen doorzoekt, en ze zodanig wijzigt dat de gebruiker (\1) blijft, maar dat yo-yo-dyne.com de host is.

 str = 'purple [email protected], blah monkey [email protected] blah dishwasher' ## re.sub(pat, replacement, str) -- returns new string with all replacements, ##  is group(1),  group(2) in the replacement print re.sub(r'(+)@(+)', r'@yo-yo-dyne.com', str) ## purple [email protected], blah monkey [email protected] blah dishwasher

Oefening

Oefening met reguliere uitdrukkingen, zie de oefening Babynamen.

admin

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.

lg