Regulära uttryck är ett kraftfullt språk för att matcha textmönster. Den här sidan ger en grundläggande introduktion till reguljära uttryck som i sig är tillräcklig för våra Pythonövningar och visar hur reguljära uttryck fungerar i Python. Pythons modul ”re” ger stöd för reguljära uttryck.

I Python skrivs en sökning med reguljära uttryck vanligtvis som:

 match = re.search(pat, str)

Metoden re.search() tar ett mönster för reguljära uttryck och en sträng och söker efter det mönstret i strängen. Om sökningen är framgångsrik returnerar search() ett matchobjekt eller None i annat fall. Därför följs sökningen vanligtvis omedelbart av en if-sats för att testa om sökningen lyckades, vilket visas i följande exempel som söker efter mönstret ”word:” följt av ett ord med tre bokstäver (detaljer nedan):

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'

Koden match = re.search(pat, str) lagrar sökresultatet i en variabel som heter ”match”. Sedan testar if-satsen matchen – om den är sann lyckades sökningen och match.group() är den matchande texten (t.ex. ”word:cat”). Om match är falsk (None för att vara mer specifik) så lyckades inte sökningen och det finns ingen matchande text.

Den ”r” i början av mönstersträngen betecknar en ”rå” pythonsträng som passerar genom backslashes utan förändring, vilket är mycket praktiskt för reguljära uttryck (Java behöver denna funktion mycket!). Jag rekommenderar att du alltid skriver mönstersträngar med ’r’ bara som en vana.

Basiska mönster

Kraften hos reguljära uttryck är att de kan ange mönster, inte bara fasta tecken. Här är de mest grundläggande mönstren som matchar enskilda tecken:

  • a, X, 9, < — vanliga tecken matchar sig själva exakt. Meta-tecken som inte matchar sig själva eftersom de har speciella betydelser är: . ^ $ * + ? { \ | ( ) (detaljer nedan)
  • . (punkt) — matchar alla enskilda tecken utom nyrad ’\n’
  • \w — (litet w) matchar ett ”ord”-tecken: en bokstav, en siffra eller ett understreck . Observera att även om ”word” är en mnemonisk beteckning för detta, matchar det bara ett enskilt ordtecken, inte ett helt ord. \W (stort W) matchar alla tecken som inte är ord.
  • \b — gräns mellan ord och icke-ord
  • \s — (liten s) matchar ett enskilt vitrymdstecken — mellanslag, nyrad, retur, tab, form . \S (stort S) matchar varje tecken som inte är ett mellanslag.
  • \t, \n, \r — tab, ny rad, retur
  • \d — decimalsiffra (vissa äldre regex-verktyg har inte stöd för men \d, men alla har stöd för \w och \s)
  • ^ = start, $ = slut — matchar strängens start eller slut
  • \ — inhiberar ett teckens ”specialitet”. Använd till exempel \. för att matcha en punkt eller \\ för att matcha ett snedstreck. Om du är osäker på om ett tecken har en speciell betydelse, till exempel ”@”, kan du sätta ett snedstreck framför det, \@@, för att försäkra dig om att det behandlas precis som ett tecken.

Basiska exempel

Joke: Vad kallar man en gris med tre ögon? piiig!

De grundläggande reglerna för sökning med reguljära uttryck efter ett mönster i en sträng är:

  • Sökningen går igenom strängen från början till slut och stannar vid den första matchningen som hittas
  • Hela mönstret måste matchas, men inte hela strängen
  • Om match = re.search(pat, str) är framgångsrik, är match inte None och i synnerhet match.group() är den matchande texten
 ## 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"

Repetition

Det blir mer intressant när du använder + och * för att ange repetition i mönstret

  • + — 1 eller flera förekomster av mönstret till vänster, t.ex. ’i+’ = ett eller flera i:n
  • * — 0 eller flera förekomster av mönstret till vänster
  • ? — matchar 0 eller 1 förekomst av mönstret till vänster

Lämst & Störst

För det första hittar sökningen den mest vänstervridna matchningen för mönstret, och för det andra försöker sökningen använda så mycket av strängen som möjligt, dvs. + och * går så långt som möjligt (+ och * sägs vara ”giriga”).

Repetition Examples

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

Emails Example

Antag att du vill hitta e-postadressen inne i strängen ”xyz [email protected] purple monkey”. Vi kommer att använda detta som ett löpande exempel för att demonstrera fler funktioner för reguljära uttryck. Här är ett försök med mönstret r’\w+@\w+’:

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

Sökningen får inte fram hela e-postadressen i det här fallet eftersom \w inte matchar ”-” eller ”.” i adressen. Vi löser detta med hjälp av funktionerna för reguljära uttryck nedan.

Vadrahörniga parenteser

Vadrahörniga parenteser kan användas för att indikera en uppsättning tecken, så de matchar ”a” eller ”b” eller ”c”. Koderna \w, \s etc. fungerar också inom hakparenteser, med det enda undantaget att punkt (.) bara betyder en bokstavlig punkt. När det gäller e-postproblemet är de fyrkantiga parenteserna ett enkelt sätt att lägga till ’.’ och ’-’ till de tecken som kan förekomma runt @ med mönstret r’+@+’ för att få fram hela e-postadressen:

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

(Fler funktioner för fyrkantiga parenteser) Du kan också använda ett bindestreck för att ange ett intervall, vilket innebär att det matchar alla små bokstäver. Om du vill använda ett bindestreck utan att ange ett intervall sätter du bindestrecket sist, t.ex. En uppåtriktad hatt (^) i början av en uppsättning med fyrkantiga hakparenteser inverterar den, vilket innebär att alla tecken utom ”a” eller ”b” är tillåtna.

Grupputdragning

Med ”grupp”-funktionen i ett reguljärt uttryck kan du plocka ut delar av den matchande texten. Anta att vi för e-postproblemet vill extrahera användarnamn och värd separat. För att göra detta lägger du till parenteser ( ) runt användarnamnet och värden i mönstret, så här: r'(+)@(+)’. I det här fallet ändrar parenteserna inte vad mönstret kommer att matcha, utan skapar istället logiska ”grupper” inom matchningstexten. Vid en lyckad sökning är match.group(1) den matchningstext som motsvarar den första vänstra parentesen, och match.group(2) är den text som motsvarar den andra vänstra parentesen. Den vanliga match.group() är fortfarande hela matchtexten som vanligt.

 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)

Ett vanligt arbetsflöde med reguljära uttryck är att du skriver ett mönster för det du letar efter och lägger till parentesgrupper för att extrahera de delar du vill ha.

findall

findall() är troligen den enskilt mest kraftfulla funktionen i re-modulen. Ovan använde vi re.search() för att hitta den första träffen för ett mönster. findall() hittar *alla* träffarna och returnerar dem som en lista med strängar, där varje sträng representerar en träff.

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

För filer kanske du har för vana att skriva en slinga för att iterera över filens rader, och du skulle sedan kunna anropa findall() på varje rad. Låt istället findall() göra iterationen åt dig — mycket bättre! Mata bara in hela filtexten i findall() och låt den returnera en lista med alla träffar i ett enda steg (kom ihåg att f.read() returnerar hela filtexten i en enda sträng):

 # 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 och grupper

Klämparentesen ( ) gruppmekanismen kan kombineras med findall(). Om mönstret innehåller 2 eller fler parentesgrupper returnerar findall() istället för att returnera en lista med strängar en lista med *tupler*. Varje tupel representerar en matchning av mönstret, och inuti tupeln finns group(1), group(2) … data. Så om 2 parentesgrupper läggs till i e-postmönstret returnerar findall() en lista med tupler, var och en med längd 2 som innehåller användarnamn och värd, t.ex. (’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

När du väl har listan med tupler kan du slinga över den för att göra en beräkning för varje tupel. Om mönstret inte innehåller några parenteser returnerar findall() en lista över funna strängar som i tidigare exempel. Om mönstret innehåller en enda uppsättning parenteser returnerar findall() en lista med strängar som motsvarar den enda gruppen. (Obskyr valfri funktion: Ibland har du paren ( ) grupperingar i mönstret, men som du inte vill extrahera. I det fallet skriver du parenteserna med ?: i början, t.ex. (?: ) och den vänstra parentesen räknas inte som ett gruppresultat.)

RE Arbetsflöde och felsökning

Regulära uttrycksmönster innehåller mycket betydelse på bara några få tecken , men de är så täta att du kan tillbringa mycket tid med att felsöka dina mönster. Konfigurera din körtid så att du enkelt kan köra ett mönster och skriva ut vad det matchar, till exempel genom att köra det på en liten testtext och skriva ut resultatet av findall(). Om mönstret inte matchar någonting kan du försöka försvaga mönstret, ta bort delar av det så att du får för många träffar. När det inte matchar någonting kan du inte göra några framsteg eftersom det inte finns något konkret att titta på. När det matchar för mycket kan du arbeta med att skärpa det stegvis för att träffa precis det du vill ha.

Optioner

Funktionerna re tar emot alternativ för att modifiera beteendet hos mönstermatchningen. Alternativflaggan läggs till som ett extra argument till search() eller findall() etc., t.ex. re.search(pat, str, re.IGNORECASE).

  • IGNORECASE — ignorerar skillnader mellan stora och små bokstäver vid matchning, så ”a” matchar både ”a” och ”A”.
  • DOTALL — tillåter att punkt (.) matchar ny rad — normalt matchar den allt annat än ny rad. Det här kan göra dig förvirrad — du tror att .* matchar allt, men som standard går den inte längre än till slutet av en rad. Observera att \s (whitespace) inkluderar nya rader, så om du vill matcha en rad med whitespace som kan inkludera en ny rad, kan du bara använda \s*
  • MULTILINE — I en sträng som består av många rader, tillåt ^ och $ att matcha början och slutet av varje rad. Normalt skulle ^/$ bara matcha början och slutet av hela strängen.

Greedy vs. Non-Greedy (valfritt)

Detta är ett valfritt avsnitt som visar en mer avancerad teknik för reguljära uttryck som inte behövs för övningarna.

Antag att du har text med taggar i den: <b>foo</b> och <i>och så vidare</i>

Antag att du försöker matcha varje tagg med mönstret ”(<.*>)” – vad matchar den först?

Resultatet är lite överraskande, men den giriga aspekten av .* gör att den matchar hela ”<b>foo</b> och <i>och så vidare</i>” som en enda stor match. Problemet är att .* går så långt som möjligt, istället för att stanna vid det första > (det är ”girigt”).

Det finns en utvidgning av reguljära uttryck som innebär att man lägger till ett ? i slutet, t.ex. .*? eller .+?, vilket ändrar dem så att de inte är giriga. Nu slutar de så fort de kan. Så mönstret ”(<.*?>)” får bara ”<b>” som första träff, och ”</b>” som andra träff, och så vidare får varje <..>-par i tur och ordning. Stilen är vanligtvis att du använder en .*?, och sedan omedelbart till höger om den letar efter någon konkret markör (> i det här fallet) som tvingar fram slutet på .*? körningen.

Förlängningen *? har sitt ursprung i Perl, och reguljära uttryck som inkluderar Perls förlängningar är kända som Perl Compatible Regular Expressions — pcre. Python innehåller stöd för pcre. Många kommandoradsverktyg etc. har en flagga där de accepterar pcre-mönster.

En äldre men allmänt använd teknik för att koda denna idé om ”alla dessa tecken utom att de slutar vid X” använder sig av stilen med fyrkantiga hakparenteser. För ovanstående skulle du kunna skriva mönstret, men istället för .* för att få fram alla tecken, använda * som hoppar över alla tecken som inte är > (den inledande ^ ”inverterar” den fyrkantiga parentesen, så den matchar alla tecken som inte finns inom parentesen).

Substitution (valfritt)

Funktionen re.sub(pat, replacement, str) letar efter alla förekomster av mönstret i den givna strängen, och ersätter dem. Ersättningssträngen kan innehålla ”\1”, ”\2” som hänvisar till texten från grupp(1), grupp(2) och så vidare från den ursprungliga matchande texten.

Här är ett exempel som söker efter alla e-postadresser och ändrar dem så att de behåller användaren (\1) men har yo-yo-dyne.com som värd.

 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

Övningsövning

För att öva på reguljära uttryck, se övningen Babynamn.

admin

Lämna ett svar

Din e-postadress kommer inte publiceras.

lg