Reguljära uttryck HOWTO¶
- Författare:
A.M. Kuchling <amk@amk.ca>
Introduktion¶
Reguljära uttryck (kallade RE, regex eller regexmönster) är i huvudsak ett litet, mycket specialiserat programmeringsspråk som är inbäddat i Python och görs tillgängligt via modulen re
. Med hjälp av detta lilla språk specificerar du reglerna för den uppsättning möjliga strängar som du vill matcha; denna uppsättning kan innehålla engelska meningar, e-postadresser, TeX-kommandon eller vad du vill. Sedan kan man ställa frågor som ”Matchar den här strängen mönstret?” eller ”Finns det en matchning för mönstret någonstans i den här strängen?”. Du kan också använda RE:er för att modifiera en sträng eller dela upp den på olika sätt.
Reguljära uttrycksmönster kompileras till en serie bytekoder som sedan exekveras av en matchningsmotor skriven i C. För avancerad användning kan det vara nödvändigt att vara noga med hur motorn kommer att exekvera en viss RE och skriva RE på ett visst sätt för att producera bytekoder som körs snabbare. Optimering behandlas inte i det här dokumentet, eftersom det kräver att du har en god förståelse för matchningsmotorns interna funktioner.
Språket för reguljära uttryck är relativt litet och begränsat, så alla möjliga strängbehandlingsuppgifter kan inte utföras med reguljära uttryck. Det finns också uppgifter som kan utföras med reguljära uttryck, men uttrycken visar sig vara mycket komplicerade. I dessa fall kan det vara bättre att skriva Python-kod för att göra bearbetningen; även om Python-kod kommer att vara långsammare än ett utarbetat reguljärt uttryck, kommer den förmodligen också att vara mer begriplig.
Enkla mönster¶
Vi börjar med att lära oss om de enklaste möjliga reguljära uttrycken. Eftersom reguljära uttryck används för att operera på strängar börjar vi med den vanligaste uppgiften: att matcha tecken.
För en detaljerad förklaring av den datavetenskap som ligger bakom reguljära uttryck (deterministiska och icke-deterministiska finita automater) kan du hänvisa till nästan vilken lärobok som helst om att skriva kompilatorer.
Matchande tecken¶
De flesta bokstäver och tecken matchar helt enkelt sig själva. Till exempel kommer det reguljära uttrycket test
att matcha strängen test
exakt. (Du kan aktivera ett skiftlägesokänsligt läge som låter RE matcha Test
eller TEST
också; mer om detta senare)
Det finns undantag från denna regel; vissa tecken är speciella metatecken, och matchar inte sig själva. Istället signalerar de att något utöver det vanliga ska matchas, eller så påverkar de andra delar av RE genom att upprepa dem eller ändra deras betydelse. En stor del av det här dokumentet ägnas åt att diskutera olika metatecken och vad de gör.
Här är en fullständig lista över metatecken; deras betydelser kommer att diskuteras i resten av denna HOWTO.
. ^ $ * + ? { } [ ] \ | ( )
De första metatecknen vi tittar på är [
och ]
. De används för att ange en teckenklass, vilket är en uppsättning tecken som du vill matcha. Tecken kan listas individuellt, eller så kan ett intervall av tecken anges genom att ange två tecken och separera dem med en '-'
. Till exempel kommer [abc]
att matcha vilket som helst av tecknen a
, b
eller c
; detta är samma sak som [a-c]
, som använder ett intervall för att uttrycka samma uppsättning tecken. Om du bara vill matcha gemena bokstäver skulle RE vara [a-z]
.
Metatecken (utom \
) är inte aktiva inom klasser. Till exempel kommer [akm$]
att matcha något av tecknen 'a'
, 'k'
, 'm'
, eller '$'
; '$'
är vanligtvis ett metatecken, men inom en teckenklass förlorar det sin speciella egenskap.
Du kan matcha de tecken som inte listas inom klassen genom att komplettera uppsättningen. Detta indikeras genom att inkludera en '^'
som första tecken i klassen. Till exempel kommer [^5]
att matcha alla tecken utom '5'
. Om caretten förekommer någon annanstans i en teckenklass har den ingen speciell betydelse. Till exempel: [5^]
kommer att matcha antingen en '5'
eller en '^'
.
Den kanske viktigaste metatecknet är backsteget, \
. Precis som i Python-strängkonstanter kan backsteget följas av olika tecken för att ange olika specialsekvenser. Den används också för att undvika alla metatecken så att du fortfarande kan matcha dem i mönster. Om du till exempel behöver matcha [
eller \
kan du föregå dem med en backsteg för att ta bort deras speciella betydelse: \[
eller \\
.
Vissa av de specialsekvenser som börjar med '\'
representerar fördefinierade uppsättningar av tecken som ofta är användbara, t.ex. uppsättningen av siffror, uppsättningen av bokstäver eller uppsättningen av allt som inte är blanksteg.
Låt oss ta ett exempel: \w
matchar alla alfanumeriska tecken. Om regex-mönstret uttrycks i byte motsvarar detta klassen [a-zA-Z0-9_]
. Om regex-mönstret är en sträng kommer w
att matcha alla tecken som markerats som bokstäver i Unicode-databasen som tillhandahålls av modulen unicodedata
. Du kan använda den mer begränsade definitionen av \w
i ett strängmönster genom att ange flaggan re.ASCII
när du kompilerar det reguljära uttrycket.
Följande lista över specialsekvenser är inte fullständig. En fullständig lista över sekvenser och utökade klassdefinitioner för Unicode-strängmönster finns i den sista delen av Regular Expression Syntax i Standard Library-referensen. I allmänhet matchar Unicode-versionerna alla tecken som finns i lämplig kategori i Unicode-databasen.
\d
Matchar alla decimalsiffror; detta motsvarar klassen
[0-9]
.\D
Motsvarar alla icke-siffriga tecken; detta motsvarar klassen
[^0-9]
.\s
Matchar alla tecken för blanksteg; detta motsvarar klassen
[ \t\n\r\f\v]
.\S
Matchar alla tecken som inte är blanksteg; detta motsvarar klassen
[^ \t\n\r\f\v]
.\w
Matchar alla alfanumeriska tecken; detta är likvärdigt med klassen
[a-zA-Z0-9_]
.\W
Matchar alla icke-alfanumeriska tecken; detta motsvarar klassen
[^a-zA-Z0-9_]
.
Dessa sekvenser kan inkluderas i en teckenklass. Till exempel är [\s,.]
en teckenklass som matchar alla blankstegstecken, eller ','
eller '.'
.
Det sista metatecknet i det här avsnittet är .
. Det matchar allt utom en ny rad, och det finns ett alternativt läge (re.DOTALL
) där det matchar även en ny rad. .
används ofta där man vill matcha ”alla tecken”.
Upprepning av saker¶
Att kunna matcha varierande uppsättningar av tecken är det första som reguljära uttryck kan göra som inte redan är möjligt med de metoder som finns tillgängliga för strängar. Men om det var den enda ytterligare förmågan hos regexes, skulle de inte vara mycket av ett framsteg. En annan möjlighet är att du kan ange att delar av RE måste upprepas ett visst antal gånger.
Det första metatecknet för upprepning av saker som vi ska titta på är *
. *
matchar inte det bokstavliga tecknet '*'
; istället anger det att det föregående tecknet kan matchas noll eller flera gånger, istället för exakt en gång.
Till exempel kommer ca*t
att matcha 'ct'
(0 'a'
tecken), 'cat'
(1 'a'
), 'caaat'
(3 'a'
tecken), och så vidare.
Upprepningar som *
är greedy; när en RE upprepas försöker matchningsmotorn upprepa den så många gånger som möjligt. Om senare delar av mönstret inte matchar, backar matchningsmotorn och försöker igen med färre upprepningar.
Ett steg-för-steg exempel gör detta mer uppenbart. Låt oss betrakta uttrycket a[bcd]*b
. Detta matchar bokstaven 'a'
, noll eller fler bokstäver från klassen [bcd]
, och slutar slutligen med 'b'
. Tänk dig nu att du matchar denna RE mot strängen 'abcbd
.
Steg |
Matchad |
Förklaring |
---|---|---|
1 |
|
”A” i ”RE” stämmer överens. |
2 |
|
Motorn matchar |
3 |
Fel |
Motorn försöker matcha |
4 |
|
Backa, så att |
5 |
Fel |
Försök med |
6 |
|
Tillbaka upp igen, så att |
6 |
|
Försök med |
Slutet av RE har nu nåtts, och den har matchat 'abcb
. Detta visar hur matchningsmotorn först går så långt den kan, och om ingen matchning hittas kommer den sedan successivt att backa tillbaka och försöka resten av RE om och om igen. Den kommer att backa tills den har försökt noll matchningar för [bcd]*
, och om det därefter misslyckas kommer motorn att dra slutsatsen att strängen inte alls matchar RE.
Ett annat upprepande metatecken är +
, som matchar en eller flera gånger. Var noga med skillnaden mellan *
och +
; *
matchar noll eller fler gånger, så det som upprepas kanske inte finns med alls, medan +
kräver minst en förekomst. För att använda ett liknande exempel, ca+t
kommer att matcha 'cat'
(1 'a'
), 'caaat'
(3 'a'
), men kommer inte att matcha 'ct'
.
Det finns ytterligare två upprepande operatorer eller kvantifierare. Frågetecknet, ?
, matchar antingen en eller noll gånger; du kan tänka på det som att markera att något är valfritt. Till exempel, home-?brew
matchar antingen 'homebrew'
eller 'home-brew'
.
Den mest komplicerade kvantifieringen är {m,n}
, där m och n är decimala heltal. Denna kvantifierare innebär att det måste finnas minst m upprepningar och högst n. Till exempel kommer a/{1,3}b
att matcha 'a/b'
, 'a//b'
och 'a///b'
. Det kommer inte att matcha 'ab'
, som inte har några snedstreck, eller 'a////b'
, som har fyra.
Du kan utelämna antingen m eller n; i så fall antas ett rimligt värde för det saknade värdet. Att utelämna m tolkas som en nedre gräns på 0, medan att utelämna n resulterar i en övre gräns på oändlighet.
Det enklaste fallet {m}
matchar föregående objekt exakt m gånger. Till exempel kommer a/{2}b
bara att matcha 'a//b'
.
Läsare med en reduktionistisk böjelse kan notera att de tre andra kvantifierarna alla kan uttryckas med denna notation. {0,}
är detsamma som *
, {1,}
är ekvivalent med +
och {0,1}
är detsamma som ?
. Det är bättre att använda *
, +
eller ?
när du kan, helt enkelt för att de är kortare och lättare att läsa.
Använda reguljära uttryck¶
Nu när vi har tittat på några enkla reguljära uttryck, hur använder vi dem egentligen i Python? Modulen re
tillhandahåller ett gränssnitt till motorn för reguljära uttryck, så att du kan sammanställa RE:er till objekt och sedan utföra matchningar med dem.
Kompilera reguljära uttryck¶
Reguljära uttryck sammanställs till mönsterobjekt, som har metoder för olika operationer som att söka efter mönstermatchningar eller utföra strängersättningar:
>>> import re
>>> p = re.compile('ab*')
>>> p
re.compile('ab*')
re.compile()
accepterar också ett valfritt flags-argument, som används för att aktivera olika specialfunktioner och syntaxvariationer. Vi kommer att gå igenom de tillgängliga inställningarna senare, men nu räcker det med ett exempel:
>>> p = re.compile('ab*', re.IGNORECASE)
RE:n skickas till re.compile()
som en sträng. REs hanteras som strängar eftersom reguljära uttryck inte är en del av Pythons kärnspråk och ingen speciell syntax skapades för att uttrycka dem. (Det finns applikationer som inte behöver REs alls, så det finns ingen anledning att uppblåsa språkspecifikationen genom att inkludera dem) Istället är modulen re
helt enkelt en C-tilläggsmodul som ingår i Python, precis som modulerna socket
eller zlib
.
Att lägga RE:er i strängar gör Python-språket enklare, men det har en nackdel som behandlas i nästa avsnitt.
Backslash-pesten¶
Som tidigare nämnts använder reguljära uttryck tecknet backslash ('\'
) för att ange speciella former eller för att tillåta att specialtecken används utan att deras speciella betydelse åberopas. Detta står i konflikt med Pythons användning av samma tecken för samma ändamål i stränglitteraler.
Låt oss säga att du vill skriva en RE som matchar strängen \section
, som kan finnas i en LaTeX-fil. För att räkna ut vad som ska skrivas i programkoden börjar du med den önskade strängen som ska matchas. Därefter måste du undkomma eventuella backslash och andra metatecken genom att föregå dem med ett backslash, vilket resulterar i strängen \\section
. Den resulterande strängen som måste skickas till re.compile()
måste vara \\section
. Men för att uttrycka detta som en Python-stränglitteral måste båda backslasharna escapas igen.
Tecken |
Steg |
---|---|
|
Textsträng som ska matchas |
|
Undangömd backslash för |
|
Escaped backslashes för en strängbördal |
Kort sagt, för att matcha ett literalt backslash måste man skriva '\\\\'
som RE-sträng, eftersom det reguljära uttrycket måste vara \\
och varje backslash måste uttryckas som \\
inuti en reguljär Python-strängliteral. I RE:er som innehåller backslash upprepade gånger leder detta till många upprepade backslash och gör de resulterande strängarna svåra att förstå.
Lösningen är att använda Pythons råa strängnotation för reguljära uttryck; backslasher hanteras inte på något speciellt sätt i en stränglitual med prefixet 'r'
, så r"\n"
är en tvåteckenssträng som innehåller '\'
och 'n'
, medan "\n"
är en enteckenssträng som innehåller en ny rad. Reguljära uttryck skrivs ofta i Python-kod med hjälp av denna råa strängnotation.
Dessutom resulterar nu speciella escape-sekvenser som är giltiga i reguljära uttryck, men inte giltiga som Python-stränglitteraler, i en DeprecationWarning
och kommer så småningom att bli en SyntaxError
, vilket innebär att sekvenserna kommer att vara ogiltiga om rå strängnotation eller escaping av backslashes inte används.
Vanlig sträng |
Rå sträng |
---|---|
|
|
|
|
|
|
Utföra matchningar¶
När du har ett objekt som representerar ett kompilerat reguljärt uttryck, vad gör du med det? Mönsterobjekt har flera metoder och attribut. Endast de mest betydelsefulla kommer att tas upp här; se re
-dokumenten för en fullständig lista.
Metod/Attribut |
Syfte |
---|---|
|
Avgör om RE matchar i början av strängen. |
|
Skanna igenom en sträng och leta efter alla platser där denna RE matchar. |
|
Hitta alla delsträngar där RE matchar och returnera dem som en lista. |
|
Hitta alla substrängar där RE matchar och returnera dem som en iterator. |
match()
och search()
returnerar None
om ingen matchning kan hittas. Om de lyckas returneras en match object-instans som innehåller information om matchningen: var den börjar och slutar, den delsträng som matchades med mera.
Du kan lära dig mer om detta genom att interaktivt experimentera med modulen re
.
Denna HOWTO använder standardtolken Python för sina exempel. Kör först Python-tolken, importera modulen re
och kompilera en RE:
>>> import re
>>> p = re.compile('[a-z]+')
>>> p
re.compile('[a-z]+')
Nu kan du försöka matcha olika strängar mot RE [a-z]+
. En tom sträng bör inte matchas alls, eftersom +
betyder ”en eller flera upprepningar”. match()
bör returnera None
i det här fallet, vilket gör att tolken inte skriver ut något. Du kan uttryckligen skriva ut resultatet av match()
för att tydliggöra detta.
>>> p.match("")
>>> print(p.match(""))
None
Låt oss nu prova det på en sträng som det ska matcha, till exempel tempo
. I det här fallet kommer match()
att returnera en match object, så du bör lagra resultatet i en variabel för senare användning.
>>> m = p.match('tempo')
>>> m
<re.Match object; span=(0, 5), match='tempo'>
Nu kan du fråga match object efter information om den matchande strängen. Match-objektinstanser har också flera metoder och attribut; de viktigaste är:
Metod/Attribut |
Syfte |
---|---|
|
Returnera den sträng som matchas av RE |
|
Returnera startpositionen för matchen |
|
Returnera slutpositionen för matchen |
|
Returnera en tupel som innehåller matchningens (start, slut) positioner |
Att prova dessa metoder kommer snart att klargöra deras betydelse:
>>> m.group()
'tempo'
>>> m.start(), m.end()
(0, 5)
>>> m.span()
(0, 5)
group()
returnerar den delsträng som matchades av RE. start()
och end()
returnerar start- och slutindex för matchningen. span()
returnerar både start- och slutindex i en enda tupel. Eftersom metoden match()
endast kontrollerar om RE matchar i början av en sträng, kommer start()
alltid att vara noll. Men metoden search()
i patterns skannar igenom strängen, så det är inte säkert att matchningen börjar vid noll i det fallet.
>>> print(p.match('::: message'))
None
>>> m = p.search('::: message'); print(m)
<re.Match object; span=(4, 11), match='message'>
>>> m.group()
'message'
>>> m.span()
(4, 11)
I faktiska program är det vanligaste sättet att lagra matchobjekt i en variabel och sedan kontrollera om det var None
. Detta ser vanligtvis ut som:
p = re.compile( ... )
m = p.match( 'strängen går här' )
if m:
print('Matchning hittades: ', m.group())
else:
print('Ingen matchning')
Två mönstermetoder returnerar alla matchningar för ett mönster. findall()
returnerar en lista med matchande strängar:
>>> p = re.compile(r'\d+')
>>> p.findall('12 drummers drumming, 11 pipers piping, 10 lords a-leaping')
['12', '11', '10']
Prefixet r
, som gör literalen till en rå sträng-literal, behövs i detta exempel eftersom escape-sekvenser i en normal ”tillagad” sträng-literal som inte känns igen av Python, till skillnad från reguljära uttryck, nu resulterar i en DeprecationWarning
och kommer så småningom att bli en SyntaxError
. Se Backslash-pesten.
findall()
måste skapa hela listan innan den kan returneras som resultat. Metoden finditer()
returnerar en sekvens av match object instanser som en iterator:
>>> iterator = p.finditer('12 drummers drumming, 11 ... 10 ...')
>>> iterator
<callable_iterator object at 0x...>
>>> for match in iterator:
... print(match.span())
...
(0, 2)
(22, 24)
(29, 31)
Funktioner på modulnivå¶
Du behöver inte skapa ett pattern-objekt och anropa dess metoder; modulen re
tillhandahåller även funktioner på högsta nivå som heter match()
, search()
, findall()
, sub()
och så vidare. Dessa funktioner tar samma argument som motsvarande pattern-metod med RE-strängen tillagd som första argument, och returnerar fortfarande antingen None
eller ett match-objekt instance.
>>> print(re.match(r'From\s+', 'Fromage amk'))
None
>>> re.match(r'From\s+', 'From amk Thu May 14 19:12:10 1998')
<re.Match object; span=(0, 5), match='From '>
Under huven skapar dessa funktioner helt enkelt ett mönsterobjekt åt dig och anropar lämplig metod på det. De lagrar också det kompilerade objektet i en cache, så att framtida anrop med samma RE inte behöver analysera mönstret om och om igen.
Ska du använda dessa funktioner på modulnivå, eller ska du hämta mönstret och anropa dess metoder själv? Om du kommer åt en regex i en loop sparar du några funktionsanrop genom att förkompilera den. Utanför loopar är det inte så stor skillnad tack vare den interna cachen.
Kompileringsflaggor¶
Med kompileringsflaggor kan du ändra vissa aspekter av hur reguljära uttryck fungerar. Flaggor finns tillgängliga i modulen re
under två namn, ett långt namn som IGNORECASE
och en kort form med en bokstav som I
. (Om du är bekant med Perls mönstermodifierare använder formerna med en bokstav samma bokstäver; kortformen av re.VERBOSE
är till exempel re.X
) Flera flaggor kan anges genom bitvis OR-ning av dem; re.I | re.M
anger till exempel både flaggorna I
och M
.
Här är en tabell över tillgängliga flaggor, följt av en mer detaljerad förklaring av var och en.
Flagga |
Betydelse |
---|---|
Gör att flera escapes som |
|
Låt |
|
Gör matchningar utan hänsyn till skiftlägeskänslighet. |
|
Gör en lokalanpassad matchning. |
|
Matchning av flera rader, påverkar |
|
Aktivera verbose RE:s, som kan organiseras på ett mer överskådligt och begripligt sätt. |
- re.I
- re.IGNORECASE
Utför matchning som inte är skiftlägeskänslig; teckenklasser och bokstavssträngar matchar bokstäver genom att ignorera skiftlägeskänslighet. Till exempel matchar
[A-Z]
även gemener. Fullständig Unicode-matchning fungerar också om inte flagganASCII
används för att inaktivera matchningar som inte är ASCII. När Unicode-mönstren[a-z]
eller[A-Z]
används i kombination med flagganIGNORECASE
matchar de de 52 ASCII-bokstäverna och 4 ytterligare icke-ASCII-bokstäver: ’İ’ (U+0130, latinsk versal I med punkt ovanför), ’ı’ (U+0131, latinsk gemen i utan punkt), ’ſ’ (U+017F, latinsk gemen lång s) och ’K’ (U+212A, Kelvin-tecken).Spam
matchar'Spam'
,'spam'
,'spAM'
eller'ſpam'
(det senare matchas endast i Unicode-läge). Denna omvandling till gemener tar inte hänsyn till den aktuella lokaliseringen; det gör den om du också anger flagganLOCALE
.
- re.L
- re.LOCALE
Gör
w
,W
,b
,B
och skiftlägesokänslig matchning beroende av aktuell locale istället för Unicode-databasen.Locales är en funktion i C-biblioteket som är avsedd att hjälpa till att skriva program som tar hänsyn till språkskillnader. Om du till exempel bearbetar kodad fransk text skulle du vilja kunna skriva
\w+
för att matcha ord, men\w
matchar bara teckenklassen[A-Za-z]
i bytesmönster; den matchar inte bytes som motsvararé
ellerç
. Om ditt system är korrekt konfigurerat och en fransk språkdräkt är vald, kommer vissa C-funktioner att tala om för programmet att byten som motsvararé
också ska betraktas som en bokstav. Om du anger flagganLOCALE
när du kompilerar ett reguljärt uttryck kommer det resulterande kompilerade objektet att använda dessa C-funktioner förw
; detta är långsammare, men gör också attw+
kan matcha franska ord som du förväntar dig. Användningen av denna flagga är avrådd i Python 3 eftersom locale-mekanismen är mycket opålitlig, den hanterar bara en ”kultur” åt gången och den fungerar bara med 8-bitars locales. Unicode-matchning är redan aktiverad som standard i Python 3 för Unicode (str)-mönster, och den kan hantera olika lokala språk.
- re.M
- re.MULTILINE
(
^
och$
har inte förklarats ännu; de kommer att introduceras i avsnittet Fler metakaraktärer.)Vanligtvis matchar
^
endast i början av strängen, och$
matchar endast i slutet av strängen och omedelbart före den nya raden (om någon) i slutet av strängen. När denna flagga anges matchar^
i början av strängen och i början av varje rad i strängen, omedelbart efter varje ny rad. På samma sätt matchar metatecknet$
antingen i slutet av strängen eller i slutet av varje rad (omedelbart före varje ny rad).
- re.S
- re.DOTALL
Gör att specialtecknet
'.'
matchar vilket tecken som helst, inklusive en ny rad; utan denna flagga matchar'.'
allt utom en ny rad.
- re.A
- re.ASCII
Få
w
,W
,b
,B
,s
ochS
att utföra matchning med enbart ASCII istället för fullständig Unicode-matchning. Detta är endast meningsfullt för Unicode-mönster och ignoreras för bytemönster.
- re.X
- re.VERBOSE
Med den här flaggan kan du skriva reguljära uttryck som är mer läsbara genom att du får mer flexibilitet i hur du kan formatera dem. När den här flaggan har angetts ignoreras blanksteg i RE-strängen, utom när blanksteget är i en teckenklass eller föregås av en obegränsad backslash; detta gör att du kan organisera och indentera RE mer tydligt. Med den här flaggan kan du också lägga in kommentarer i en RE som ignoreras av motorn; kommentarer markeras med en
'#'
som varken är i en teckenklass eller föregås av ett okapslat backslash.Här är till exempel en RE som använder
re.VERBOSE
; ser du hur mycket lättare den är att läsa?charref = re.compile(r""" &[#] # Start of a numeric entity reference ( 0[0-7]+ # Octal form | [0-9]+ # Decimal form | x[0-9a-fA-F]+ # Hexadecimal form ) ; # Trailing semicolon """, re.VERBOSE)
Utan verbose-inställningen skulle RE se ut så här:
charref = re.compile("&#(0[0-7]+" "|[0-9]+" "|x[0-9a-fA-F]+);")
I exemplet ovan har Pythons automatiska konkatenering av stränglitteraler använts för att bryta upp RE i mindre bitar, men det är fortfarande svårare att förstå än den version som använder
re.VERBOSE
.
Mer mönsterkraft¶
Hittills har vi bara gått igenom en del av funktionerna i reguljära uttryck. I det här avsnittet går vi igenom några nya metatecken och hur man använder grupper för att hämta delar av den text som matchades.
Fler metakaraktärer¶
Det finns några metakaraktärer som vi inte har gått igenom ännu. De flesta av dem kommer att tas upp i detta avsnitt.
Några av de återstående metatecken som skall diskuteras är zero-width assertions. De gör inte att motorn avancerar genom strängen, utan de förbrukar inga tecken alls och lyckas eller misslyckas helt enkelt. Till exempel är b
ett påstående om att den aktuella positionen ligger vid en ordgräns; positionen ändras inte alls av b
. Detta innebär att påståenden med nollbredd aldrig bör upprepas, för om de matchar en gång på en given plats kan de uppenbarligen matchas ett oändligt antal gånger.
|
Alternation, eller operatorn ”eller”. Om A och B är reguljära uttryck kommer
A|B
att matcha alla strängar som matchar antingen A eller B.|
har mycket låg prioritet för att det ska fungera på ett rimligt sätt när du växlar strängar med flera tecken.Crow|Servo
kommer att matcha antingen'Crow'
eller'Servo'
, inte'Cro'
, ett'w'
eller ett'S'
, och'ervo'
.Om du vill matcha en literal
'|'
använder du\|
eller omsluter den inne i en teckenklass, som i[|]
.^
Matchar i början av raderna. Om inte flaggan
MULTILINE
har angetts, matchar detta endast i början av strängen. IMULTILINE
-läget matchar detta även omedelbart efter varje ny rad i strängen.Om du till exempel vill matcha ordet
From
endast i början av en rad, är RE att använda^From
.>>> print(re.search('^From', 'From Here to Eternity')) <re.Match object; span=(0, 4), match='From'> >>> print(re.search('^From', 'Reciting From Memory')) None
Om du vill matcha en bokstavlig
'^'
använder du^
.$
Matchar i slutet av en rad, vilket definieras som antingen slutet av strängen eller en plats som följs av ett tecken för ny rad.
>>> print(re.search('}$', '{block}')) <re.Match object; span=(6, 7), match='}'> >>> print(re.search('}$', '{block} ')) None >>> print(re.search('}$', '{block}\n')) <re.Match object; span=(6, 7), match='}'>
Om du vill matcha en bokstavlig
'$'
använder du\$
eller omsluter den med en teckenklass, som i[$]
.\A
Matchar endast i början av strängen. När det inte är i
MULTILINE
-läge ärA
och^
i praktiken samma sak. IMULTILINE
-läget är de olika:A
matchar fortfarande bara i början av strängen, men^
kan matcha på vilken plats som helst i strängen som följer efter ett newline-tecken.\z
Matchar endast i slutet av strängen.
\Z
Samma som
z
. För kompatibilitet med gamla Python-versioner.\b
Ordgräns. Detta är en assertion med noll bredd som endast matchar i början eller slutet av ett ord. Ett ord definieras som en sekvens av alfanumeriska tecken, så slutet av ett ord indikeras av blanksteg eller ett icke-alfanumeriskt tecken.
Följande exempel matchar
class
endast när det är ett helt ord; det kommer inte att matcha när det ingår i ett annat ord.>>> p = re.compile(r'\bclass\b') >>> print(p.search('no class at all')) <re.Match object; span=(3, 8), match='class'> >>> print(p.search('the declassified algorithm')) None >>> print(p.search('one subclass is')) None
Det finns två saker du bör komma ihåg när du använder den här speciella sekvensen. För det första är detta den värsta kollisionen mellan Pythons stränglitteraler och sekvenser för reguljära uttryck. I Pythons stränglitteraler är
b
backspace-tecknet, ASCII-värde 8. Om du inte använder råa strängar kommer Python att konverterab
till ett backsteg, och din RE kommer inte att matcha som du förväntar dig att den ska göra. Följande exempel ser likadant ut som vår föregående RE, men utelämnar'r
framför RE-strängen.>>> p = re.compile('\bclass\b') >>> print(p.search('no class at all')) None >>> print(p.search('\b' + 'class' + '\b')) <re.Match object; span=(0, 7), match='\x08class\x08'>
För det andra, i en teckenklass, där det inte finns någon användning för detta påstående, representerar
b
backspace-tecknet, för kompatibilitet med Pythons stränglitteraler.\B
Detta är motsatsen till
b
och matchar bara när den aktuella positionen inte ligger vid en ordgräns.
Gruppering¶
Ofta behöver man mer information än bara om RE:n matchar eller inte. Reguljära uttryck används ofta för att dissekera strängar genom att skriva en RE som är uppdelad i flera undergrupper som matchar olika komponenter av intresse. En rubrikrad i RFC-822 delas t.ex. upp i ett rubriknamn och ett värde, åtskilda av ett ':'
, så här:
Från: author@example.com
Användar-Agent: Thunderbird 1.5.0.9 (X11/20061227)
MIME-version: 1.0
Till: editor@example.com
Detta kan hanteras genom att skriva ett reguljärt uttryck som matchar en hel rubrikrad och har en grupp som matchar rubrikens namn och en annan grupp som matchar rubrikens värde.
Grupper markeras med metatecknen '('
, ')'
. '('
och ')'
har ungefär samma betydelse som de har i matematiska uttryck; de grupperar de uttryck som finns inuti dem, och du kan upprepa innehållet i en grupp med en kvantifierare, till exempel *
, +
, ?
eller {m,n}
. Till exempel kommer (ab)*
att matcha noll eller fler upprepningar av ab
.
>>> p = re.compile('(ab)*')
>>> print(p.match('ababababab').span())
(0, 10)
Grupper som indikeras med '('
, ')'
fångar också start- och slutindex för texten som de matchar; detta kan hämtas genom att skicka ett argument till group()
, start()
, end()
, och span()
. Grupperna är numrerade med början på 0. Grupp 0 finns alltid; det är hela RE, så match object -metoderna har alla grupp 0 som standardargument. Senare ska vi se hur man uttrycker grupper som inte fångar det textavsnitt som de matchar.
>>> p = re.compile('(a)b')
>>> m = p.match('ab')
>>> m.group()
'ab'
>>> m.group(0)
'ab'
Undergrupperna numreras från vänster till höger, från 1 och uppåt. Grupper kan vara kapslade; för att bestämma antalet räknar man bara de inledande parentestecknen från vänster till höger.
>>> p = re.compile('(a(b)c)d')
>>> m = p.match('abcd')
>>> m.group(0)
'abcd'
>>> m.group(1)
'abc'
>>> m.group(2)
'b'
group()
kan ges flera gruppnummer åt gången, i vilket fall den kommer att returnera en tupel som innehåller motsvarande värden för dessa grupper.
>>> m.group(2,1,2)
('b', 'abc', 'b')
Metoden groups()
returnerar en tupel som innehåller strängarna för alla undergrupper, från 1 upp till hur många som helst.
>>> m.groups()
('abc', 'b')
Med hjälp av bakåtreferenser i ett mönster kan du ange att innehållet i en tidigare fångad grupp också måste finnas på den aktuella platsen i strängen. Till exempel kommer 1
att lyckas om det exakta innehållet i grupp 1 kan hittas på den aktuella positionen, och misslyckas annars. Kom ihåg att Pythons stränglitteraler också använder ett backslash följt av siffror för att tillåta att godtyckliga tecken inkluderas i en sträng, så se till att använda en rå sträng när du införlivar bakåtreferenser i en RE.
Följande RE upptäcker till exempel dubbla ord i en sträng:
>>> p = re.compile(r'\b(\w+)\s+\1\b')
>>> p.search('Paris in the the spring').group()
'the the'
Sådana här backreferenser är inte ofta användbara för att bara söka igenom en sträng — det finns få textformat som upprepar data på det här sättet — men du kommer snart att upptäcka att de är mycket användbara när du utför strängsubstitutioner.
Icke-fångande och namngivna grupper¶
I komplicerade RE:er kan många grupper användas, både för att fånga upp intressanta delsträngar och för att gruppera och strukturera själva RE:n. I komplexa RE:er blir det svårt att hålla reda på gruppnumren. Det finns två funktioner som hjälper till att lösa detta problem. Båda använder en gemensam syntax för tillägg till reguljära uttryck, så vi tittar på det först.
Perl 5 är välkänt för sina kraftfulla tillägg till standardmässiga reguljära uttryck. För dessa nya funktioner kunde Perl-utvecklarna inte välja nya metatecken med ett enda tangenttryck eller nya specialsekvenser som börjar med \
utan att göra Perls reguljära uttryck förvirrande olika från standardmässiga reguljära uttryck. Om de till exempel hade valt &
som nytt metatecken skulle gamla uttryck anta att &
var ett vanligt tecken och inte skulle ha undvikit det genom att skriva \&
eller [&]
.
Den lösning som Perl-utvecklarna valde var att använda (?...)
som utvidgningssyntax. ?
omedelbart efter en parentes var ett syntaxfel eftersom ?
inte skulle ha något att upprepa, så detta medförde inga kompatibilitetsproblem. Tecknen omedelbart efter ?
anger vilket tillägg som används, så (?=foo)
är en sak (ett positivt lookahead-assertion) och (?:foo)
är något annat (en icke-fångande grupp som innehåller underuttrycket foo
).
Python stöder flera av Perls tillägg och lägger till en tilläggssyntax till Perls tilläggssyntax. Om det första tecknet efter frågetecknet är ett P
vet du att det är ett tillägg som är specifikt för Python.
Nu när vi har tittat på den allmänna syntaxen för tillägg kan vi återgå till de funktioner som förenklar arbetet med grupper i komplexa RE:er.
Ibland vill du använda en grupp för att beteckna en del av ett reguljärt uttryck, men du är inte intresserad av att hämta gruppens innehåll. Du kan göra detta faktum explicit genom att använda en icke-fångande grupp: (?:...)
, där du kan ersätta ...
med vilket annat reguljärt uttryck som helst.
>>> m = re.match("([abc])+", "abc")
>>> m.groups()
('c',)
>>> m = re.match("(?:[abc])+", "abc")
>>> m.groups()
()
Förutom det faktum att du inte kan hämta innehållet i det som gruppen matchade, beter sig en icke-fångande grupp exakt likadant som en fångande grupp; du kan lägga in vad som helst i den, upprepa den med ett metatecken för upprepning som *
och nesta den i andra grupper (fångande eller icke-fångande). (?:...)
är särskilt användbart när du ändrar ett befintligt mönster, eftersom du kan lägga till nya grupper utan att ändra hur alla andra grupper är numrerade. Det bör nämnas att det inte finns någon prestandaskillnad i sökningen mellan fångande och icke-fångande grupper; ingen av formerna är snabbare än den andra.
En mer betydelsefull funktion är namngivna grupper: i stället för att hänvisa till dem med nummer kan grupper hänvisas till med ett namn.
Syntaxen för en namngiven grupp är ett av de Python-specifika tilläggen: (?P<name>...)
. name är naturligtvis namnet på gruppen. Namngivna grupper beter sig precis som fångande grupper, och associerar dessutom ett namn med en grupp. Metoderna match object som hanterar fångande grupper accepterar alla antingen heltal som hänvisar till gruppen med nummer eller strängar som innehåller den önskade gruppens namn. Namngivna grupper ges fortfarande nummer, så du kan hämta information om en grupp på två sätt:
>>> p = re.compile(r'(?P<word>\b\w+\b)')
>>> m = p.search( '(((( Lots of punctuation )))' )
>>> m.group('word')
'Lots'
>>> m.group(1)
'Lots'
Dessutom kan du hämta namngivna grupper som en ordbok med groupdict()
:
>>> m = re.match(r'(?P<first>\w+) (?P<last>\w+)', 'Jane Doe')
>>> m.groupdict()
{'first': 'Jane', 'last': 'Doe'}
Namngivna grupper är praktiska eftersom de låter dig använda namn som är lätta att komma ihåg, istället för att behöva komma ihåg siffror. Här är ett exempel på RE från modulen imaplib
:
InternalDate = re.compile(r'INTERNALDATE "'
r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-'
r'(?P<year>[0-9][0-9][0-9][0-9])'
r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
r'"')
Det är uppenbarligen mycket enklare att hämta m.group('zonem')
, istället för att behöva komma ihåg att hämta grupp 9.
Syntaxen för backreferenser i ett uttryck som (...)\1
hänvisar till gruppens nummer. Det finns naturligtvis en variant som använder gruppnamnet i stället för numret. Detta är ett annat Python-tillägg: (?P=namn)
indikerar att innehållet i gruppen som heter namn återigen ska matchas vid den aktuella punkten. Det reguljära uttrycket för att hitta dubblerade ord, \b(\w+)\s+\1\b
kan också skrivas som \b(?P<word>\w+)\s+(?P=ord)\b
:
>>> p = re.compile(r'\b(?P<word>\w+)\s+(?P=word)\b')
>>> p.search('Paris in the the spring').group()
'the the'
Förutseende påståenden¶
Ett annat nollbreddsassertion är lookahead-assertion. Lookahead-assertions finns i både positiv och negativ form och ser ut så här:
(?=...)
Positivt påstående om lookahead. Detta lyckas om det ingående reguljära uttrycket, som här representeras av
...
, framgångsrikt matchar på den aktuella platsen, och misslyckas annars. Men när det inneslutna uttrycket har prövats går matchningsmotorn inte vidare alls; resten av mönstret prövas precis där påståendet började.(?!...)
Negativt framåtblickande påstående. Detta är motsatsen till det positiva påståendet; det lyckas om det ingående uttrycket inte matchar vid den aktuella positionen i strängen.
För att konkretisera detta ska vi titta på ett fall där en lookahead är användbar. Tänk på ett enkelt mönster för att matcha ett filnamn och dela upp det i ett basnamn och ett tillägg, åtskilda av en .
. I till exempel news.rc
är news
basnamnet och rc
är filnamnets tillägg.
Mönstret för att matcha detta är ganska enkelt:
.*[.].*$
Lägg märke till att .
måste behandlas speciellt eftersom det är ett metatecken, så det är inuti en teckenklass som bara matchar det specifika tecknet. Lägg också märke till det efterföljande $
; detta läggs till för att säkerställa att resten av strängen måste inkluderas i tillägget. Detta reguljära uttryck matchar foo.bar
och autoexec.bat
och endmail.cf
och printers.conf
.
Tänk nu på att komplicera problemet lite; vad händer om du vill matcha filnamn där tillägget inte är bat
? Några felaktiga försök:
.*[.][^b].*$
Det första försöket ovan försöker utesluta bat
genom att kräva att det första tecknet i tillägget inte är ett b
. Detta är fel, eftersom mönstret inte heller matchar foo.bar
.
.*[.]([^b]..|.[^a].|..[^t])$
Uttrycket blir rörigare när du försöker lappa ihop den första lösningen genom att kräva att något av följande fall ska matcha: det första tecknet i tillägget är inte b
; det andra tecknet är inte a
; eller det tredje tecknet är inte t
. Detta accepterar foo.bar
och avvisar autoexec.bat
, men det kräver ett tillägg på tre bokstäver och accepterar inte ett filnamn med ett tillägg på två bokstäver som endmail.cf
. Vi ska komplicera mönstret igen i ett försök att lösa det.
.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$
I det tredje försöket görs den andra och tredje bokstaven valfria för att möjliggöra matchning av tillägg som är kortare än tre tecken, t.ex. sendmail.cf
.
Mönstret börjar bli riktigt komplicerat nu, vilket gör det svårt att läsa och förstå. Än värre, om problemet ändras och du vill utesluta både bat
och exe
som tillägg, skulle mönstret bli ännu mer komplicerat och förvirrande.
En negativ framåtblickande prognos skär igenom all denna förvirring:
.*[.](?!bat$)[^.]*$
Den negativa lookahead betyder: om uttrycket bat
inte matchar vid denna punkt, prova resten av mönstret; om bat$
matchar, kommer hela mönstret att misslyckas. Den efterföljande $
krävs för att säkerställa att något som sample.batch
, där tillägget endast börjar med bat
, tillåts. [^.]*
ser till att mönstret fungerar när det finns flera punkter i filnamnet.
Det är nu enkelt att utesluta ett annat filnamnstillägg; lägg bara till det som ett alternativ i påståendet. Följande mönster utesluter filnamn som slutar på antingen bat
eller exe
:
.*[.](?!bat$|exe$)[^.]*$
Modifiera strängar¶
Hittills har vi bara gjort sökningar mot en statisk sträng. Reguljära uttryck används också ofta för att modifiera strängar på olika sätt, med hjälp av följande mönstermetoder:
Metod/Attribut |
Syfte |
---|---|
|
Dela upp strängen i en lista och dela upp den där RE matchar |
|
Hitta alla delsträngar där RE matchar och ersätt dem med en annan sträng |
|
Gör samma sak som |
Delning av strängar¶
Metoden split()
för ett mönster delar upp en sträng där RE matchar och returnerar en lista över delarna. Den liknar metoden split()
för strängar men är mycket mer generell när det gäller de avgränsare som du kan dela upp med; sträng split()
stöder bara uppdelning med blanksteg eller med en fast sträng. Som du kan förvänta dig finns det också en funktion på modulnivå re.split()
.
- .split(string[, maxsplit=0])
Dela upp sträng efter matchningarna i det reguljära uttrycket. Om fångande parenteser används i RE, kommer deras innehåll också att returneras som en del av den resulterande listan. Om maxsplit är ett annat värde än noll utförs högst maxsplit uppdelningar.
Du kan begränsa antalet uppdelningar som görs genom att ange ett värde för maxsplit. När maxsplit inte är noll görs högst maxsplit uppdelningar och resten av strängen returneras som det sista elementet i listan. I följande exempel är avgränsaren en valfri sekvens av icke-alfanumeriska tecken.
>>> p = re.compile(r'\W+')
>>> p.split('This is a test, short and sweet, of split().')
['This', 'is', 'a', 'test', 'short', 'and', 'sweet', 'of', 'split', '']
>>> p.split('This is a test, short and sweet, of split().', 3)
['This', 'is', 'a', 'test, short and sweet, of split().']
Ibland är man inte bara intresserad av vad texten mellan avgränsarna är, utan behöver också veta vad avgränsaren var. Om fångande parenteser används i RE returneras även deras värden som en del av listan. Jämför följande anrop:
>>> p = re.compile(r'\W+')
>>> p2 = re.compile(r'(\W+)')
>>> p.split('This... is a test.')
['This', 'is', 'a', 'test', '']
>>> p2.split('This... is a test.')
['This', '... ', 'is', ' ', 'a', ' ', 'test', '.', '']
Funktionen på modulnivå re.split()
lägger till den RE som ska användas som första argument, men är i övrigt densamma.
>>> re.split(r'[\W]+', 'Words, words, words.')
['Words', 'words', 'words', '']
>>> re.split(r'([\W]+)', 'Words, words, words.')
['Words', ', ', 'words', ', ', 'words', '.', '']
>>> re.split(r'[\W]+', 'Words, words, words.', 1)
['Words', 'words, words.']
Sök och ersätt¶
En annan vanlig uppgift är att hitta alla matchningar för ett mönster och ersätta dem med en annan sträng. Metoden sub()
tar ett ersättningsvärde, som kan vara antingen en sträng eller en funktion, och den sträng som ska bearbetas.
- .sub(replacement, string[, count=0])
Returnerar strängen som erhålls genom att ersätta de längst till vänster liggande icke-överlappande förekomsterna av RE i sträng med ersättningen replacement. Om mönstret inte hittas returneras sträng oförändrad.
Det valfria argumentet count är det maximala antalet mönsterförekomster som ska ersättas; count måste vara ett icke-negativt heltal. Standardvärdet 0 innebär att alla förekomster ersätts.
Här är ett enkelt exempel på användning av metoden sub()
. Den ersätter färgnamn med ordet colour
:
>>> p = re.compile('(blue|white|red)')
>>> p.sub('colour', 'blue socks and red shoes')
'colour socks and colour shoes'
>>> p.sub('colour', 'blue socks and red shoes', count=1)
'colour socks and red shoes'
Metoden subn()
gör samma sak, men returnerar en 2-tupel som innehåller det nya strängvärdet och antalet ersättningar som utfördes:
>>> p = re.compile('(blue|white|red)')
>>> p.subn('colour', 'blue socks and red shoes')
('colour socks and colour shoes', 2)
>>> p.subn('colour', 'no colours at all')
('no colours at all', 0)
Tomma tändstickor ersätts endast om de inte ligger intill en tidigare tom tändsticka:
>>> p = re.compile('x*')
>>> p.sub('-', 'abxd')
'-a-b--d-'
Om replacement är en sträng behandlas alla backslash-escapes i den. Det innebär att \n
konverteras till ett enda tecken för ny rad, \r
konverteras till en vagnsretur och så vidare. Okända escape-tecken som &
lämnas därhän. Bakreferenser, t.ex. 6
, ersätts med den delsträng som matchas av motsvarande grupp i RE. Detta gör att du kan införliva delar av originaltexten i den resulterande ersättningssträngen.
Detta exempel matchar ordet section
följt av en sträng innesluten i {
, }
och ändrar section
till subsection
:
>>> p = re.compile('section{ ( [^}]* ) }', re.VERBOSE)
>>> p.sub(r'subsection{\1}','section{First} section{second}')
'subsection{First} subsection{second}'
Det finns också en syntax för att hänvisa till namngivna grupper som definieras av syntaxen (?P<name>...)
. \g<name>
kommer att använda den delsträng som matchas av gruppen med namnet namn
, och g<number>
använder motsvarande gruppnummer. \g<2>
är därför likvärdigt med 2
, men är inte tvetydigt i en ersättningssträng som \g<2>0
. (20
skulle tolkas som en referens till grupp 20, inte som en referens till grupp 2 följt av det bokstavliga tecknet '0
) Följande ersättningar är alla likvärdiga, men använd alla tre varianterna av ersättningssträngen:
>>> p = re.compile('section{ (?P<name> [^}]* ) }', re.VERBOSE)
>>> p.sub(r'subsection{\1}','section{First}')
'subsection{First}'
>>> p.sub(r'subsection{\g<1>}','section{First}')
'subsection{First}'
>>> p.sub(r'subsection{\g<name>}','section{First}')
'subsection{First}'
replacement kan också vara en funktion, vilket ger dig ännu mer kontroll. Om replacement är en funktion anropas funktionen för varje icke-överlappande förekomst av pattern. Vid varje anrop får funktionen ett match-objekt-argument för matchningen och kan använda denna information för att beräkna den önskade ersättningssträngen och returnera den.
I följande exempel översätter ersättningsfunktionen decimaler till hexadecimal:
>>> def hexrepl(match):
... "Return the hex string for a decimal number"
... value = int(match.group())
... return hex(value)
...
>>> p = re.compile(r'\d+')
>>> p.sub(hexrepl, 'Call 65490 for printing, 49152 for user code.')
'Call 0xffd2 for printing, 0xc000 for user code.'
När funktionen re.sub()
används på modulnivå skickas mönstret som det första argumentet. Mönstret kan anges som ett objekt eller som en sträng; om du behöver ange flaggor för reguljära uttryck måste du antingen använda ett mönsterobjekt som första parameter eller använda inbäddade modifierare i mönstersträngen, t.ex. sub("(?i)b+", "x", "bbbb BBBB")
returnerar 'x x'
.
Vanliga problem¶
Reguljära uttryck är ett kraftfullt verktyg för vissa tillämpningar, men på vissa sätt är deras beteende inte intuitivt och ibland beter de sig inte på det sätt som du kanske förväntar dig att de ska göra. I det här avsnittet pekar vi på några av de vanligaste fallgroparna.
Använda strängmetoder¶
Ibland är det ett misstag att använda modulen re
. Om du matchar en fast sträng, eller en klass med ett enda tecken, och inte använder några re
-funktioner som t.ex. flaggan IGNORECASE
, kanske du inte behöver använda reguljära uttryck fullt ut. Strings har flera metoder för att utföra operationer med fasta strängar och de är vanligtvis mycket snabbare, eftersom implementeringen är en enda liten C-loop som har optimerats för ändamålet, istället för den stora, mer generaliserade reguljära uttrycksmotorn.
Ett exempel kan vara att ersätta en enda fast sträng med en annan; till exempel kan du ersätta word
med deed
. re.sub()
verkar vara den funktion som ska användas för detta, men överväg replace()
-metoden. Observera att replace()
också kommer att ersätta word
inuti ord, vilket gör wordfish
till deedfish
, men den naiva RE word
skulle ha gjort det också. (För att undvika att utföra substitutionen på delar av ord, skulle mönstret behöva vara bword\b
, för att kräva att word
har en ordgräns på vardera sidan. Detta tar jobbet bortom replace()
förmågor)
En annan vanlig uppgift är att ta bort varje förekomst av ett enda tecken från en sträng eller ersätta det med ett annat enda tecken. Du kan göra detta med något som re.sub('\n', ' ', S)
, men translate()
kan göra båda uppgifterna och kommer att vara snabbare än någon operation med reguljära uttryck kan vara.
Kort sagt, innan du vänder dig till re
-modulen bör du överväga om ditt problem kan lösas med en snabbare och enklare strängmetod.
match() kontra search()¶
Funktionen match()
kontrollerar bara om RE matchar i början av strängen medan search()
söker framåt genom strängen efter en matchning. Det är viktigt att hålla denna distinktion i minnet. Kom ihåg att match()
endast kommer att rapportera en lyckad matchning som börjar på 0; om matchningen inte skulle börja på noll kommer match()
inte att rapportera den.
>>> print(re.match('super', 'superstition').span())
(0, 5)
>>> print(re.match('super', 'insuperable'))
None
Å andra sidan kommer search()
att skanna framåt genom strängen och rapportera den första matchningen den hittar.
>>> print(re.search('super', 'superstition').span())
(0, 5)
>>> print(re.search('super', 'insuperable').span())
(2, 7)
Ibland frestas du att fortsätta använda re.match()
, och bara lägga till .*
framför din RE. Motstå denna frestelse och använd re.search()
istället. Kompilatorn för reguljära uttryck gör en del analyser av RE:er för att snabba upp processen med att leta efter en matchning. En sådan analys räknar ut vad det första tecknet i en matchning måste vara; till exempel måste ett mönster som börjar med Crow
matcha som börjar med en 'C'
. Analysen gör att sökmotorn snabbt kan skanna igenom strängen och leta efter starttecknet, och bara försöka få en fullständig matchning om ett 'C
hittas.
Att lägga till .*
motverkar denna optimering och kräver att man skannar till slutet av strängen och sedan går tillbaka för att hitta en matchning för resten av RE. Använd re.search()
istället.
Girig kontra icke-girig¶
När man upprepar ett reguljärt uttryck, som i a*
, blir resultatet att så mycket som möjligt av mönstret konsumeras. Detta faktum slår ofta fel när man försöker matcha ett par balanserade avgränsare, t.ex. de vinkelparenteser som omger en HTML-tagg. Det naiva mönstret för att matcha en enda HTML-tagg fungerar inte på grund av den giriga karaktären hos .*
.
>>> s = '<html><head><title>Title</title>'
>>> len(s)
32
>>> print(re.match('<.*>', s).span())
(0, 32)
>>> print(re.match('<.*>', s).group())
<html><head><title>Title</title>
RE matchar '<'
i '<html>'
, och .*
förbrukar resten av strängen. Det finns dock fortfarande mer kvar i RE, och >
kan inte matcha i slutet av strängen, så motorn för reguljära uttryck måste backa tecken för tecken tills den hittar en matchning för >
. Den slutliga matchningen sträcker sig från '<'
i '<html>'
till '>'
i '</title>'
, vilket inte är vad du vill.
I det här fallet är lösningen att använda de icke-giriga kvantifierarna *?
, +?
, ??
eller {m,n}?
, som matchar så lite text som möjligt. I exemplet ovan försöks '>'
omedelbart efter att den första '<'
matchar, och när det misslyckas går motorn fram ett tecken i taget och försöker igen med '>'
vid varje steg. Detta ger precis rätt resultat:
>>> print(re.match('<.*?>', s).group())
<html>
(Observera att parsning av HTML eller XML med reguljära uttryck är smärtsamt. Snabba och enkla mönster hanterar vanliga fall, men HTML och XML har specialfall som bryter mot det uppenbara reguljära uttrycket; när du väl har skrivit ett reguljärt uttryck som hanterar alla möjliga fall kommer mönstren att vara mycket komplicerade. Använd en HTML- eller XML-parsermodul för sådana uppgifter)
Använda re.VERBOSE¶
Vid det här laget har du förmodligen märkt att reguljära uttryck är en mycket kompakt notation, men de är inte särskilt läsbara. RE med måttlig komplexitet kan bli långa samlingar av bindestreck, parenteser och metatecken, vilket gör dem svåra att läsa och förstå.
För sådana RE:s kan det vara till hjälp att ange flaggan re.VERBOSE
vid kompilering av det reguljära uttrycket, eftersom det gör att du kan formatera det reguljära uttrycket tydligare.
Flaggan re.VERBOSE
har flera effekter. Whitespace i det reguljära uttrycket som inte är inom en teckenklass ignoreras. Detta innebär att ett uttryck som dog | cat
motsvarar det mindre läsbara dog|cat
, men [a b]
kommer fortfarande att matcha tecknen 'a'
, 'b'
eller ett mellanslag. Dessutom kan du lägga in kommentarer inuti en RE; kommentarer sträcker sig från ett #
-tecken till nästa nya rad. När detta används med strängar med trippelcitat kan RE:er formateras på ett snyggare sätt:
pat = re.compile(r"""
\s* # Hoppa över ledande blanksteg
(?P<header>[^:]+) # Huvudnamn
\s* : # Blanksteg, och ett kolon
(?P<value>.*?) # Huvudets värde -- *? används för att
# förlora följande efterföljande blanksteg
\s*$ # Efterföljande blanksteg till slutet av raden
""", re.VERBOSE)
Detta är mycket mer läsbart än:
pat = re.compile(r"\s*(?P<header>[^:]+)\s*:(?P<value>.*?)\s*$")
Feedback¶
Reguljära uttryck är ett komplicerat ämne. Hjälpte det här dokumentet dig att förstå dem? Fanns det delar som var oklara, eller problem som du stötte på som inte täcktes här? Om så är fallet, skicka gärna förslag på förbättringar till författaren.
Den mest kompletta boken om reguljära uttryck är nästan säkert Jeffrey Friedls Mastering Regular Expressions, utgiven av O’Reilly. Tyvärr koncentrerar den sig uteslutande på Perl och Javas smaker av reguljära uttryck och innehåller inte något Python-material alls, så det kommer inte att vara användbart som referens för programmering i Python. (Den första utgåvan täckte Pythons nu borttagna regex
-modul, vilket inte kommer att hjälpa dig mycket.) Överväg att kolla in det från ditt bibliotek.