regular expressions python tutorial

Als data scientist zul je soms bepaalde patronen in tekst moeten herkennen. Stel je wilt in een Data Science vraagstuk bijvoorbeeld alleen de cijfers uit een string halen voor een analyse, of je wilt bijvoorbeeld prijzen of e-mailadressen van webpagina's scrapen. Dit kun je realiseren met Regular Expressions in Python.

Regular expressions zorgen er ook voor dat zoekmachines als Google de content waar jij op zoek naar bent kunnen indexeren. En als je een nieuw wachtwoord aanmaakt op een website dan kan de website dankzij regular expressions aangeven of je bijvoorbeeld een hoofdletter, een cijfer, of een speciaal karakter mist voor een sterk wachtwoord.

Een regular expression (ook wel 'regex') is een serie van karakters waarmee je als het ware een zoekopdracht voor een bepaald patroon meegeeft.

In deze blog gaan we in op de mogelijkheden die regular expressions in Python scripts ons bieden:

Hoe importeer je de "re module"?

Om regular expressions in Python te gebruiken is het van belang dat ten eerste Python is geïnstalleerd. Binnen Python is er de "re module" waarin "re" natuurlijk staat voor 'regular expression'.

In [1]:
import re

Binnen deze module is veel nuttige functionaliteit beschikbaar, maar om deze blog behapbaar te houden zullen we ons beperken tot de veelgebruikte re.search(regular expression, string) functie.

Met deze functie is het mogelijk om met behulp van een regular expression te zoeken of het patroon voorkomt in de string. We zullen steeds deze functie toepassen in voorbeelden.

Welke mogelijkheden heb je om karakters te matchen?

In onderstaande tabel vind je een samenvatting van de functies die metacharacters hebben binnen regular expressions. Als je deze tabel voor het eerst ziet kan het verwarrend overkomen, daarom geven we van ieder metacharacter voorbeelden onder de tabel.

MetacharacterWelke functie heeft dit?
.Een punt 'matcht' ieder karakter behalve een nieuwe regel
^
  • Zoekt naar matches aan de start van een string
  • Kan ook gebruikt worden om te zoeken naar "al het andere". [^0-9] matcht bijvoorbeeld alles behalve een cijfer.
$Zoekt naar een match aan het eind van een string
*Matcht nul of meer repetities van iets
+Matcht één of meer repetities van iets
?
  • Matcht één of nul repetities van iets
  • Wordt toegepast om matches non-greedy te maken
{getal}Matcht wanneer 'getal' aantal keer een patroon voorkomt in een string
\Voorkomt dat een metacharacter als metacharacter wordt gezien door Python
[]Binnen blokhaken specificeer je een character class zoals bijvoorbeeld [a-z]
()Met haakjes creëer je een groep
|Regex A | regex B zorgt ervoor dat de totale regex matcht op A óf B

De "." in regular expressions

De punt heeft een functie die gemakkelijk te begrijpen is. Met een punt match je namelijk ieder teken behalve een nieuwe regel. Stel we hebben een string waarin diverse jaartallen voorkomen en we willen 10 tekens selecteren na het woord 'jaartallen'. Dan kun je dat doen door 10 punten neer te zetten.

In [2]:
verhaal_met_jaartallen = "In de geschiedenis van Nederland spelen de jaartallen 1824, 1846, 1849, 1940, 2004, en 1949 een belangrijke rol"
print(re.search('jaartallen..........', verhaal_met_jaartallen))
<re.Match object; span=(43, 63), match='jaartallen 1824, 184'>

Je kunt zien dat een punt zowel spaties als een comma als diverse cijfers heeft gematcht. De totale match komt neer op 'jaartallen 1824, 184'.

Met vertrouwen waardevolle inzichten halen uit data? Schrijf je in voor een van onze trainingen.


"[]" blokhaken in regular expressions

Met blokhaken kun je een teken-set specificeren om te matchen. Zo kun je bijvoorbeeld [a-z] gebruiken om één kleine letter te matchen, [0-9] om een cijfer te matchen en [A-Z] om een hoofdletter te matchen. Het is ook mogelijk e.e.a. te combineren door bijvoorbeeld [0-9a-z].

Stel je bent geïnteresseerd of onze string een jaartal bevat tussen 2000 en 2010 dan kun je dat als volgt achterhalen.

In [3]:
verhaal_met_jaartallen = "In de geschiedenis van Nederland spelen de jaartallen 1824, 1846, 1849, 1940, 2004, en 1949 een belangrijke rol"
print(re.search('200[1-9]', verhaal_met_jaartallen))
<re.Match object; span=(78, 82), match='2004'>

We zien dat er een match is met het jaartal 2004.

Je zou ook kunnen zoeken naar een jaartal uit de 17e eeuw met de volgende regular expression.

In [4]:
verhaal_met_jaartallen = "In de geschiedenis van Nederland spelen de jaartallen 1824, 1846, 1849, 1940, 2004, en 1949 een belangrijke rol"
print(re.search('17[0-9][0-9]', verhaal_met_jaartallen))
None

Er is geen match gevonden op jaartallen beginnend met 17.

Als je specifiek naar een blokhaak wil zoeken dan kan dat door de blokhaak als eerste teken toe te voegen of door de functie van het metacharacter er af te halen door er een "\" voor te zetten.

In [5]:
print(re.search('\[', 'string met een [ blokaak'))
<re.Match object; span=(15, 16), match='['>

Het dakje "^" in regular expressions

Het dakje is op verschillende manieren te gebruiken. Ten eerste kun je het binnen blokhaken gebruiken, bijvoorbeeld [^0-9] waarmee ieder teken gematcht wordt behalve de cijfers nul tot en met negen. Dit kun je op dezelfde manier gebruiken voor letterreeksen zoals in onderstaand voorbeeld, waar we matchen op ieder teken behalve op de reeks "a" tot en met "g".

In [6]:
print(re.search('[^a-g]', 'abcdefghijk'))
<re.Match object; span=(7, 8), match='h'>

We zien dat de letter 'h' matcht omdat het het eerste teken is dat niet binnen de reeks a-g valt.

Een andere toepassing van het dakje is het matchen op het begin van een string. Onderstaand voorbeeld geeft weer hoe dit werkt.

In [7]:
print(re.search('^bcd', 'abcdefghijk'))
print(re.search('bcd', 'abcdefghijk'))
None
<re.Match object; span=(1, 4), match='bcd'>

Omdat de string niet met 'bcd' start vindt de regex '^bcd' geen match terwijl 'bcd' wél gematcht wordt.

Het "$"-teken in regular expressions

Het dollarteken heeft binnen Python regular expressions een vergelijkbare functie als het dakje. Anders dan het dakje matcht het dollarteken op het einde van een string. In onderstaand voorbeeld laten we zien hoe dit werkt:

In [8]:
print(re.search('ijk$', 'abcdefghijk'))
print(re.search('ijk$', 'ijkabcdefgh'))
<re.Match object; span=(8, 11), match='ijk'>
None

We zien er wel een match is als 'ijk' aan het eind van de string staat, maar niet als de string er mee begint.

Het sterretje "*" in regular expressions

Aankomende drie metacharacters voor regular expressions zeggen allemaal iets over hoeveel characters er gematcht worden. Een sterretje (*) geeft aan dat iets nul of zo vaak als je wilt mag voorkomen.

Stel dat we willen weten of er in onze string een patroon bestaat dat begrint met 'de' en eindigt met 'van'. De karakters hiertussen mogen alles zijn. We hebben geleerd dat een punt (.) matcht op alle tekens (behalve een nieuwe regel) en met het sterretje (*) kunnen we aangeven dat de string zo lang mag zijn als je wilt.

In [9]:
verhaal_met_jaartallen = "In de geschiedenis van Nederland spelen de jaartallen 1824, 1846, 1849, 1940, 2004, en 1949 een belangrijke rol"
print(re.search('de.*van', verhaal_met_jaartallen))
<re.Match object; span=(3, 22), match='de geschiedenis van'>

We zien dat er een match is en dat het gedeelte ".*" uit de regex matcht op " geschiedenis ".

Ook als er nul tekens matchen op ".*" dan geeft de regex een resultaat zoals je in onderstaand voorbeeld kan zien.

In [10]:
verhaal_met_jaartallen = "In de geschiedenis van Nederland spelen de jaartallen 1824, 1846, 1849, 1940, 2004, en 1949 een belangrijke rol"
print(re.search('geschie.*denis', verhaal_met_jaartallen))
<re.Match object; span=(6, 18), match='geschiedenis'>

De "+" in regular expressions

Waar het sterretje nul of meer keer iets matcht, match je met het plusteken één of meer keer.

In [11]:
print(re.search('vo+rbeeld', 'voooooorbeeld'))
<re.Match object; span=(0, 13), match='voooooorbeeld'>

In dit voorbeeld matcht de Python regex alle o's dankzij het plusteken.

Het vraagteken "?" in regular expressions

Het vraagteken matcht nul of één keer iets. In onderstaand voorbeeld maken we de 'd' uit het woord voorbeeld optioneel in de regex.

In [12]:
print(re.search('voorbeeld?', 'voorbeel'))
print(re.search('voorbeeld?', 'voorbeeld'))
<re.Match object; span=(0, 8), match='voorbeel'>
<re.Match object; span=(0, 9), match='voorbeeld'>

We zien op beiden een match omdat de 'd' wel of niet mag voorkomen in de string.

Zoals je hebt kunnen zien matchen het sterretje en het plusteken een zo lang mogelijke reeks als mogelijk. Dit wordt 'greedy' genoemd. Als je één van deze tekens opvolgt met een vraagteken dan wordt de kortst mogelijke match gepakt.

In [13]:
print(re.search('o+', 'voooooorbeeld'))
print(re.search('o+?', 'voooooorbeeld'))
print(re.search('o.*', 'voooooorbeeld'))
print(re.search('o.*?', 'voooooorbeeld'))
print(re.search('vo?', 'voooooorbeeld'))
print(re.search('vo??', 'voooooorbeeld'))
<re.Match object; span=(1, 7), match='oooooo'>
<re.Match object; span=(1, 2), match='o'>
<re.Match object; span=(1, 13), match='oooooorbeeld'>
<re.Match object; span=(1, 2), match='o'>
<re.Match object; span=(0, 2), match='vo'>
<re.Match object; span=(0, 1), match='v'>

We zien dat het vraagteken de matches zo kort mogelijk (non-greedy) maakt.

"{}" accolades in regular expressions

Tussen accolades kun je precies aangeven hoe vaak een patroon voor moet komen voor een match. Zo kun je jaartallen met vier cijfers achter elkaar vinden met [0-9]{4}. Onderstaand voorbeeld matcht het eerste jaartal uit onze string.

In [14]:
verhaal_met_jaartallen = "In de geschiedenis van Nederland spelen de jaartallen 1824, 1846, 1849, 1940, 2004, en 1949 een belangrijke rol"
print(re.search('[0-9]{4}', verhaal_met_jaartallen))
<re.Match object; span=(54, 58), match='1824'>

De "\" in regular expressions

Het is al kort genoemd maar de "\" kan gebruikt worden om metacharacters van hun speciale functie(s) te ontdoen. Als je in een string wil zoeken op een van de volgende tekens dan kan dat dankzij de "\".

. ^ $ * + ? { } [ ] \ | ( )

In [15]:
print(re.search('\^', 'raar ^ teken'))
<re.Match object; span=(5, 6), match='^'>

Waar het dakje normaliter aangeeft dat we matchen op het begin van een string, ontdoen we het dakje van deze functie dankzij de slash. Als we een "\" zelf willen matchen in een string dan moeten we het teken zelf van de functie ontdoen. Dit doe je het netst in een raw string (dit doe je door een "r" toe te voegen voor het aanhalingsteken waarmee je een string opent).

In [16]:
print(re.search(r'\\', 'slash \ matchen'))
<re.Match object; span=(6, 7), match='\\'>

We zien dat er een match wordt teruggegeven. In de match wordt tevens gebruikgemaakt van de notatie waarin duidelijk wordt gemaakt dat het om een "\" gaat zonder de 'normale' regex functie. Vandaar dat hij er twee keer staat.

"()" haken in regular expressions

Tussen haken kun je een groep maken. Zo kun je bijvoorbeeld zoeken naar de eerste twee jaartallen in een string, waarbij je de twee jaartallen als groep aanmerkt.

In [17]:
verhaal_met_jaartallen = "In de geschiedenis van Nederland spelen de jaartallen 1824, 1846, 1849, 1940, 2004, en 1949 een belangrijke rol"
jaartallen = re.search('([0-9]{4}).+?([0-9]{4})', verhaal_met_jaartallen)
print(jaartallen)
print(jaartallen.groups())
print(jaartallen.group(1))
print(jaartallen.group(2))
<re.Match object; span=(54, 64), match='1824, 1846'>
('1824', '1846')
1824
1846

Hier gebruiken we verschillende dingen tegelijk. We zoeken naar jaartallen met vier cijfers met de regex [0-9]{4} en maken er een groep van door het tussen haakjes te zetten. Met ".+?" zoeken we naar één of meer tekens tussen de twee groepen in. Het vraagteken zorgt ervoor dat dit gedeelte non-greedy is. Het laatste stukje van de Python regex matcht het tweede jaartal als groep.

We zien in de output dat je .groups() en .group() kunt gebruiken om alleen de waarde(n) uit de groep(en) op te roepen.

De "|" in regular expressions

Met een verticale streep kun je een óf statement in een regex brengen. Zo matchen we met onderstaande regex op één van drie genoemde jaartallen.

In [18]:
verhaal_met_jaartallen = "In de geschiedenis van Nederland spelen de jaartallen 1824, 1846, 1849, 1940, 2004, en 1949 een belangrijke rol"
print(re.search('1364|1846|2008', verhaal_met_jaartallen))
<re.Match object; span=(60, 64), match='1846'>

Het jaartal 1846 geeft een match.

Met vertrouwen waardevolle inzichten halen uit data? Schrijf je in voor een van onze trainingen.


Conclusie

Je hebt eindeloos mogelijkheden met regular expressions. De échte kracht komt naar boven wanneer bovenstaande concepten gecombineerd worden. Zoek je bijvoorbeeld eurobedragen van iedere grootte afgerond op twee cijfers achter de comma? Dan kun je die nu eenvoudig uit stukken tekst plukken met onderstaande Python regular expression. Kun jij hem volgen?

In [19]:
eurogetal = 'hier staat een getal in euros €4512321.9232'
print(re.search('€[0-9]+.[0-9]{2}', eurogetal))
<re.Match object; span=(30, 41), match='€4512321.92'>

Of je gaat nog wat ingewikkelder door emailadressen uit tekst te plukken met de volgende regex. Mooi toch?

In [20]:
emailadres = 'hier staat een emailadres persoon@organisatie.nl'
print(re.search('[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}', emailadres))
<re.Match object; span=(26, 48), match='persoon@organisatie.nl'>

Wil je nog veel meer leren over Python en de mogelijkheden voor data analyse? Schrijf je dan in voor onze Python cursus voor data science, onze machine learning training, of voor onze data science opleiding en leer met vertrouwen te programmeren en analyseren in Python. Nadat je een van onze trainingen hebt gevolgd kun je zelfstandig verder aan de slag. Je kunt ook altijd even contact opnemen als je een vraag hebt.

Download één van onze opleidingsbrochures voor meer informatie

 

by: