Nacházíte se zde: Domů ‣ Ponořme se do Pythonu 3 ‣
Úroveň obtížnosti: ♦♦♦♢♢
❝ Some people, when confronted with a problem, think “I know, I’ll use regular expressions.” Now they have two problems. ❞
(Když se někteří lidé setkají s problémem, pomyslí si: „Já vím! Použiji regulární výrazy.“ V tom okamžiku mají problémy dva.)
— Jamie Zawinski
Získávání malých kousků textu z velkých bloků textu představuje výzvu. Pythonovské řetězcové objekty poskytují metody pro vyhledávání a náhrady: index()
, find()
, split()
, count()
, replace()
atd. Ale použití těchto metod je omezeno na nejjednodušší případy. Tak například metoda index()
hledá jediný, pevně zadaný řetězec a vyhledávání je vždy citlivé na velikost písmen. Pokud chceme řetězec s vyhledat bez ohledu na velikost písmen, musíme zavolat s.lower()
(převod na malá písmena) nebo s.upper()
(převod na velká písmena) a zajistit odpovídající převod prohledávaných řetězců. Metody replace()
and split()
mají stejná omezení.
Pokud svého cíle můžete dosáhnout metodami řetězcového objektu, měli byste je použít. Jsou rychlé, jednoduché a snadno čitelné. O rychlém, jednoduchém a čitelném kódu bychom se mohli bavit ještě dlouho. Ale pokud se přistihnete, že používáte velké množství různých řetězcových funkcí a příkazů if
, abyste zvládli speciální případy, nebo pokud musíte kombinovat volání split()
a join()
, abyste řetězce rozsekávali na kousky a zase je slepovali, v takových případech může být vhodné přejít k regulárním výrazům.
Regulární výrazy představují mocný a (většinou) standardizovaný způsob vyhledávání, náhrad a rozkladu textu se složitými vzorci znaků. Syntaxe regulárních výrazů je sice obtížná a nepodobná normálnímu kódu, ale výsledek může být nakonec čitelnější než řešení používající mnoho řetězcových funkcí. Existují dokonce způsoby, jak lze do regulárních výrazů vkládat komentáře. To znamená, že jejich součástí může být podrobná dokumentace.
☞Pokud už jste regulární výrazy používali v jiných jazycích (jako jsou Perl, JavaScript nebo PHP), bude vám pythonovská syntaxe připadat důvěrně známá. Abyste získali přehled o dostupných funkcích a jejich argumentech, přečtěte si shrnutí v dokumentaci modulu
re
.
⁂
Následující série příkladů byla inspirována problémem, který jsem před několika lety řešil v práci. Potřeboval jsem vyčistit a standardizovat adresy ulic, které byly vyexportované z původního systému, ještě před jejich importem do nového systému. (Vidíte? Já si ty věci jen tak nevymýšlím. Ony jsou ve skutečnosti užitečné.) Tento příklad ukazuje, jak jsem na to šel.
>>> s = '100 NORTH MAIN ROAD' >>> s.replace('ROAD', 'RD.') ① '100 NORTH MAIN RD.' >>> s = '100 NORTH BROAD ROAD' >>> s.replace('ROAD', 'RD.') ② '100 NORTH BRD. RD.' >>> s[:-4] + s[-4:].replace('ROAD', 'RD.') ③ '100 NORTH BROAD RD.' >>> import re ④ >>> re.sub('ROAD$', 'RD.', s) ⑤ '100 NORTH BROAD RD.'
'ROAD'
vždycky zkrátilo na 'RD.'
. Na první pohled jsem si myslel, že je to dost jednoduché, takže prostě použiji řetězcovou metodu replace()
. Koneckonců, všechna data už byla převedena na velká písmena, takže problém citlivosti na velikost písmen odpadl. A vyhledávaný řetězec 'ROAD'
je konstantní. A v tomto klamně jednoduchém případě s.replace()
samozřejmě funguje.
'ROAD'
. Jednou jde o část jména ulice 'BROAD'
a jednou o samostatné slovo. Metoda replace()
tyto dva výskyty najde a slepě je oba nahradí. A já jen pozoruji, jak se mé adresy kazí.
'ROAD'
vyřešili, můžeme se uchýlit k něčemu takovému: hledání a náhradu 'ROAD'
budeme provádět jen v posledních čtyřech znacích adresy (s[-4:]
) a zbytek řetězce ponecháme beze změny (s[:-4]
). Ale už sami vidíte, že to začíná být těžkopádné. Například už jen to, že řešení závisí na délce řetězce, který nahrazujeme. (Pokud bychom chtěli nahradit 'STREET'
zkratkou 'ST.'
, museli bychom napsat s[:-6]
a s[-6:].replace(...)
.) Líbilo by se vám, kdybyste se k tomu museli za šest měsíců vrátit a hledat chybu? Jsem si jistý, že ne.
re
.
'ROAD$'
. Jde o jednoduchý regulární výraz, ke kterému 'ROAD'
pasuje jen v případě, když se vyskytne na konci řetězce. Znak $
vyjadřuje „konec řetězce“. (Existuje také odpovídající znak, stříška ^
, která znamená „začátek řetězce“.) Voláním funkce re.sub()
hledáme v řetězci s regulární výraz 'ROAD$'
a nahradíme jej řetězcem 'RD.'
. Nalezne se tím ROAD
na konci řetězce s, ale nenalezne se podřetězec ROAD
, který je součástí slova BROAD
. To se totiž nachází uprostřed řetězce s.
Pokračujme v mém příběhu o čištění adres. Brzy jsem zjistil, že předchozí řešení, kdy 'ROAD'
lícuje s koncem adresy, není dost dobré. Ne všechny adresy totiž obsahují údaj, že se jedná o ulici. Některé adresy jednoduše končí jménem ulice. Většinou to vyšlo, ale pokud by se ulice jmenovala 'BROAD'
, pak by regulární výraz pasoval na 'ROAD'
, které se nachází na konci řetězce, ale je součástí slova 'BROAD'
. A to není to, co bych potřeboval.
>>> s = '100 BROAD' >>> re.sub('ROAD$', 'RD.', s) '100 BRD.' >>> re.sub('\\bROAD$', 'RD.', s) ① '100 BROAD' >>> re.sub(r'\bROAD$', 'RD.', s) ② '100 BROAD' >>> s = '100 BROAD ROAD APT. 3' >>> re.sub(r'\bROAD$', 'RD.', s) ③ '100 BROAD ROAD APT. 3' >>> re.sub(r'\bROAD\b', 'RD.', s) ④ '100 BROAD RD. APT 3'
'ROAD'
, který se nacházel na konci řetězce a navíc tvořil samostatné slovo (a ne část nějakého delšího slova). V regulárním výrazu to vyjádříme zápisem \b
, který má význam „hranice slova se musí vyskytnout právě tady“ (b jako boundary). V Pythonu je to komplikované skutečností, že znak '\'
musíme v řetězci vyjádřit zvláštním způsobem. (Tento znak se anglicky nazývá též „escape character“ a používá se pro zápis zvláštních posloupností. Má tedy zvláštní význam. Pokud jej chceme použít v prostém významu, musíme jej také zapsat jako „escape“ sekvenci. Prakticky to znamená, že jej musíme zdvojit.) Někdy se to označuje jako mor zpětných lomítek. Je to jeden z důvodů, proč se psaní regulárních výrazů v Perlu jeví snadnější než v jazyce Python. Negativní stránkou Perlu je míchání vlastních regulárních výrazů a odlišností při jejich zápisu. Takže pokud se někde projevuje chyba, dá se někdy obtížně odhadnout, zda je to chyba syntaxe nebo chyba ve vašem regulárním výrazu.
r
před uvozovacím znakem použijeme to, čemu se říká surový řetězec (ve smyslu přírodní, nezpracovaný; anglicky raw string). Tím Pythonu říkáme, že se v tomto řetězci nepoužívají speciální posloupnosti (escape sequence). Zápis '\t'
vyjadřuje tabulační znak, ale r'\t'
se opravdu chápe jako znak \
následovaný písmenem t
. Pokud budete pracovat s regulárními výrazy, doporučuji vám vždy používat surové řetězce. V opačném případě dospějete velmi rychle k velkým zmatkům. (Regulární výrazy jsou už i tak dost matoucí.)
'ROAD'
jako samostatné slovo, ale to se nenacházelo na konci. Za označením ulice se totiž nacházelo číslo bytu. A protože se 'ROAD'
nenacházelo na úplném konci řetězce, nepasovalo to s regulárním výrazem, takže celé volání re.sub()
neprovedlo vůbec žádnou náhradu a vrátil se původní řetězec, což nebylo to, co jsem chtěl.
$
a přidal jsem další \b
. Teď už regulární výraz můžeme číst „vyhledej samostatné slovo 'ROAD'
kdekoliv v řetězci“, ať už je to na konci, na začátku nebo někde uprostřed.
⁂
Římská čísla už jste určitě viděli, i když jste je možná nerozpoznali. Mohli jste je vidět u starých filmů nebo televizních pořadů jako „Copyright MCMXLVI
“ místo „Copyright 1946
“, nebo na stěnách knihoven a univerzit („založeno MDCCCLXXXVIII
“ místo „založeno 1888
“ ). Mohli jste je vidět v různých číslováních a odkazech na literaturu. Jde o systém zápisu čísel, který se opravdu datuje do dob starého římského impéria (proto ten název).
U římských čísel se používá sedm znaků, které se opakují a kombinují různými způsoby, aby vyjádřily číselnou hodnotu.
I = 1
V = 5
X = 10
L = 50
C = 100
D = 500
M = 1000
Následují základní pravidla pro konstrukci římských čísel:
I
je 1
, II
je rovno 2
a III
znamená 3
. VI
se rovná 6
(doslova „5
a 1
“), VII
je 7
a VIII
je 8
.
I
, X
, C
a M
) se mohou opakovat nanejvýš třikrát. Hodnotu 4
musíme vyjádřit odečtením od dalšího vyššího pětkového znaku. Hodnotu 4
nemůžeme zapsat jako IIII
. Místo toho ji musíme zapsat jako IV
(„o 1
méně než 5
“). 40
se zapisuje jako XL
(„o 10
méně než 50
“), 41
jako XLI
, 42
jako XLII
, 43
jako XLIII
a následuje 44
jako XLIV
(„o 10
méně než 50
a k tomu o 1
méně než 5
“).
9
musíme vyjádřit odečtením od dalšího vyššího desítkového znaku: 8
zapíšeme jako VIII
, ale 9
zapíšeme IX
(„o 1
méně než 10
“) a ne jako VIIII
(protože znak I
nemůžeme opakovat čtyřikrát). 90
je XC
, 900
je CM
.
10
se vždy zapisuje jako X
a nikdy jako VV
. 100
je vždy C
, nikdy LL
.
DC
znamená 600
, ale CD
je úplně jiné číslo (400
, „o 100
méně než 500
“). CI
je 101
; IC
není dokonce vůbec platné římské číslo (protože 1
nemůžeme přímo odčítat od 100
; musíme to napsat jako XCIX
, „o 10
méně než 100
a k tomu o 1
méně než 10
“).
Jak bychom vlastně mohli ověřit, zda je libovolný řetězec platným římským číslem? Podívejme se na to po jednotlivých číslicích. Římské číslice se vždycky píší od největších k nejmenším. Začněme tedy u nejvyšších, na místě tisícovek. U čísel 1000 a vyšších se tisícovky vyjadřují jako řada znaků M
.
>>> import re >>> pattern = '^M?M?M?$' ① >>> re.search(pattern, 'M') ② <_sre.SRE_Match object at 0106FB58> >>> re.search(pattern, 'MM') ③ <_sre.SRE_Match object at 0106C290> >>> re.search(pattern, 'MMM') ④ <_sre.SRE_Match object at 0106AA38> >>> re.search(pattern, 'MMMM') ⑤ >>> re.search(pattern, '') ⑥ <_sre.SRE_Match object at 0106F4A8>
^
zajistí vazbu další části výrazu na začátek řetězce. Pokud bychom jej nepoužili, pak by vzorek pasoval nezávisle na tom, kde by se znaky M
nacházely. A to bychom nechtěli. Chceme si být jistí ním, že pokud se nějaké znaky M
najdou, musí se nacházet na začátku řetězce. Zápis M?
odpovídá nepovinnému výskytu jednoho znaku M
. A protože se opakuje třikrát, odpovídá výraz výskytu žádného až tří znaků M
za sebou. Znak $
odpovídá konci řetězce. Když to dáme dohromady se znakem ^
na začátku, znamená to, že vzorek musí odpovídat celému řetězci. Znakům M
nemůže žádný jiný znak předcházet a ani za nimi nemůže následovat.
re
je funkce search()
. Ta přebírá regulární výraz (pattern) a řetězec ('M'
) a zkusí, jestli k sobě pasují. Pokud je shoda nalezena, vrátí funkce search()
objekt, který nabízí různé metody k popisu výsledku. Pokud ke shodě nedojde, vrací funkce search()
hodnotu None
, což je pythonovská hodnota null (nil, nic). V tomto okamžiku nás zajímá jen to, zda vzorek pasuje. Abychom mohli odpovědět, stačí se podívat na návratovou hodnotu funkce search()
. Řetězec 'M'
odpovídá regulárnímu výrazu, protože první nepovinný znak M
sedí a druhý a třetí nepovinný znak M
se ignoruje.
'MM'
vyhovuje, protože první a druhý nepovinný znak M
pasují a třetí M
se ignoruje.
'MMM'
vyhovuje, protože všechny tři znaky M
pasují.
'MMMM'
nevyhovuje. Všechny tři znaky M
pasují, ale pak regulární výraz trvá na tom, že řetězec musí skončit (protože je to předepsáno znakem $
). Jenže řetězec ještě nekončí (protože následuje čtvrté M
). Takže search()
vrací None
.
M
jsou nepovinné.
Kontrola stovek je obtížnější než kontrola tisícovek. Je to tím, že v závislosti na hodnotě existuje několik vzájemně se vylučujících způsobů, kterými mohou být stovky vyjádřeny.
100 = C
200 = CC
300 = CCC
400 = CD
500 = D
600 = DC
700 = DCC
800 = DCCC
900 = CM
Takže tu máme čtyři možné vzory:
CM
CD
C
(nula v případě, kdy má být na místě stovek 0).
D
následované žádným až třemi znaky C
.
Poslední dva vzory můžeme zkombinovat:
D
následované žádným až třemi znaky C
.
Následující příklad ukazuje, jak můžeme u římských čísel ověřit zápis stovek.
>>> import re >>> pattern = '^M?M?M?(CM|CD|D?C?C?C?)$' ① >>> re.search(pattern, 'MCM') ② <_sre.SRE_Match object at 01070390> >>> re.search(pattern, 'MD') ③ <_sre.SRE_Match object at 01073A50> >>> re.search(pattern, 'MMMCCC') ④ <_sre.SRE_Match object at 010748A8> >>> re.search(pattern, 'MCMC') ⑤ >>> re.search(pattern, '') ⑥ <_sre.SRE_Match object at 01071D98>
^
) a potom místo pro tisícovky (M?M?M?
). V závorkách je poté uvedena nová část, která definuje sadu tří vzájemně výlučných vzorků oddělených svislými čarami: CM
, CD
a D?C?C?C?
(což vyjadřuje nepovinné D
následované žádným nebo třemi znaky C
). Analyzátor (parser) regulárního výrazu kontroluje každý z těchto vzorků v daném pořadí (zleva doprava), zvolí první, který situaci odpovídá, a ostatní ignoruje.
'MCM'
vyhovuje, protože pasuje první M
, druhý a třetí znak M
vzorku se ignorují. Následující podřetězec CM
odpovídá prvnímu vzorku v závorce (takže části vzorku CD
a D?C?C?C?
se neuvažují). MCM
je římské číslo vyjadřující hodnotu 1900
.
'MD'
vyhovuje, protože pasuje první M
, druhé a třetí M
se ignorují. Vzorek D?C?C?C?
pasuje k D
(každý z následujících tří znaků C
je nepovinný, takže se ignorují). MD
je římské číslo vyjadřující 1500
.
'MMMCCC'
testem prošel. Všechny tři znaky M
pasují. Následující vzorek D?C?C?C?
pasuje k podřetězci CCC
(znak D
je nepovinný a ignoruje se). MMMCCC
je římské číslo vyjadřující hodnotu 3300
.
'MCMC'
nevyhovuje. První znak M
pasuje, druhé a třetí M
se ignorují. Následující CM
vyhovuje, ale poté vzorek předepisuje znak $
, který nesedí, protože ještě nejsme na konci řetězce. (Pořád nám zbývá nezpracovaný znak C
.) Poslední znak C
nelze napasovat ani na část vzorku D?C?C?C?
, protože ta se vzájemně vylučuje s částí vzorku CM
, která se již použila.
M
jsou nepovinné a ignorují se. Prázdný řetězec dále vyhovuje i části vzorku D?C?C?C?
, protože všechny znaky jsou nepovinné a ignorují se.
Uffff! Vidíte, jak se mohou regulární výrazy rychle stát nechutnými? A to jsme zatím vyřešili části římských čísel jen pro tisíce a stovky. Ale pokud jste zatím vše sledovali, budou pro vás desítky a jednotky jednoduché, protože u nich použijeme naprosto stejný přístup. Ale podívejme se ještě na další možnost vyjádření vzorku.
⁂
{n,m}
V předcházející podkapitole jsme pracovali se vzorkem, ve kterém se mohly stejné znaky opakovat až třikrát. V regulárních výrazech existuje ještě jiný způsob, jak to vyjádřit. Někteří lidé jej považují za čitelnější. Podívejme se nejdříve na způsoby, které jsme použili v předcházejícím příkladu.
>>> import re >>> pattern = '^M?M?M?$' >>> re.search(pattern, 'M') ① <_sre.SRE_Match object at 0x008EE090> >>> re.search(pattern, 'MM') ② <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, 'MMM') ③ <_sre.SRE_Match object at 0x008EE090> >>> re.search(pattern, 'MMMM') ④ >>>
M
, ale ne s druhým a s třetím M
(což je v pořádku, protože jsou nepovinná). Potom následuje konec řetězce.
M
, ale ne s třetím M
(ale to je v pořádku, protože je nepovinné). Poté pasuje i konec řetězce.
M
a s koncem řetězce.
M
, ale poté nenásleduje předepsaný konec řetězce (protože tu máme ještě jedno nepasující M
). To znamená, že vzorek nesedí a vrací se None
.
>>> pattern = '^M{0,3}$' ① >>> re.search(pattern, 'M') ② <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, 'MM') ③ <_sre.SRE_Match object at 0x008EE090> >>> re.search(pattern, 'MMM') ④ <_sre.SRE_Match object at 0x008EEDA8> >>> re.search(pattern, 'MMMM') ⑤ >>>
M
a pak musí být konec řetězce.“ Na místě 0 a 3 mohou být uvedena libovolná čísla. Pokud chceme předepsat „nejméně jeden, ale ne víc než tři znaky M
“, můžeme napsat M{1,3}
.
M
a s koncem řetězce.
M
a s koncem řetězce.
M
a s koncem řetězce.
M
, ale poté nedochází ke shodě s předpisem pro konec řetězce. Tento regulární výraz předepisuje maximálně tři znaky M
následované koncem řetězce, ale řetězec obsahuje čtyři, takže vzorek nepasuje a vrací se None
.
Rozšiřme tedy regulární výraz pro kontrolu římských čísel o kontrolu na místě desítek a jednotek. Následující příklad ukazuje, jak můžeme kontrolovat desítky.
>>> pattern = '^M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)$' >>> re.search(pattern, 'MCMXL') ① <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, 'MCML') ② <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, 'MCMLX') ③ <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, 'MCMLXXX') ④ <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, 'MCMLXXXX') ⑤ >>>
M
, následuje shoda s CM
, poté s XL
a s předpisem pro konec řetězce. Připomeňme si, že syntaxe (A|B|C)
vyjadřuje „odpovídá právě jednomu z A, B nebo C“. Došlo ke shodě s XL
, takže se ignorují možnosti XC
a L?X?X?X?
. Poté byl nalezen konec řetězce. MCMXL
je římské číslo vyjadřující hodnotu 1940
.
M
, následuje shoda s CM
a pak s L?X?X?X?
. Co se týká části L?X?X?X?
, vyhovuje jí L
a přeskakují se všechny tři nepovinné znaky X
. Poté se dostáváme ke konci řetězce. MCML
je římské číslo vyjadřující hodnotu 1950
.
M
, následuje shoda s CM
, poté s nepovinným L
, s prvním nepovinným X
, pak se přeskočí druhé a třetí nepovinné X
a následuje očekávaný konec řetězce. MCMLX
je římské číslo vyjadřující hodnotu 1960
.
M
, potom CM
, pak následuje nepovinné L
a všechna tři nepovinná X
a vyžadovaný konec řetězce. MCMLXXX
je římské číslo vyjadřující hodnotu 1980
.
M
, potom CM
, pak tu máme nepovinné L
a všechna tři nepovinná X
, ale poté dochází k selhání předpokladu konce řetězce, protože nám zbývá ještě jedno X
, se kterým jsme nepočítali. Takže celý regulární výraz selhává (nepasuje) a vrací se None
. MCMLXXXX
není platné římské číslo.
Výraz pro test jednotek vytvoříme stejným způsobem. Ušetřím vás detailů a ukážu vám jen konečný výsledek.
>>> pattern = '^M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)(IX|IV|V?I?I?I?)$'
So what does that look like using this alternate {n,m}
syntax? This example shows the new syntax.
>>> pattern = '^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$' >>> re.search(pattern, 'MDLV') ① <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, 'MMDCLXVI') ② <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, 'MMMDCCCLXXXVIII') ③ <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, 'I') ④ <_sre.SRE_Match object at 0x008EEB48>
M
a následně s předpisem D?C{0,3}
. U posledního podvýrazu dochází ke shodě s nepovinným D
a s nulou ze tří možných znaků C
. Posuňme se dál. Zde pasuje podvýraz L?X{0,3}
, protože vyhoví nepovinné L
a nula ze tří možných znaků X
. Další kousek řetězce vyhovuje podvýrazu V?I{0,3}
, protože je nalezeno nepovinné V
a nula ze tří možných znaků I
. A na závěr nastává očekávaný konec řetězce. MDLV
je římské číslo vyjadřující hodnotu 1555
.
M
, pak s D?C{0,3}
s jedním D
a s jedním ze tří možných znaků C
. Pokračujeme L?X{0,3}
s jedním L
a jedním ze tří možných znaků X
. A dále tu máme V?I{0,3}
s jedním V
a jedním ze tří možných znaků I
. Pasuje i očekávaný konec řetězce. MMDCLXVI
je římské číslo vyjadřující hodnotu 2666
.
M
, pak je tu D?C{0,3}
s jedním D
a s třemi ze tří možných znaků C
. Pokračujeme L?X{0,3}
s jedním L
a s třemi ze tří možných znaků X
. A dále se uplatní V?I{0,3}
s jedním V
a s třemi ze tří možných znaků I
. A očekávaný konec řetězce. MMMDCCCLXXXVIII
je římské číslo vyjadřující hodnotu 3888
. Současně je to největší římské číslo, které můžete napsat bez použití rozšířené syntaxe.
M
, pak pasuje D?C{0,3}
— přeskočení nepovinného D
a absence znaku C
(nula až tři možné výskyty). Pokračujeme shodou s podvýrazem L?X{0,3}
přeskočením nepovinného L
a přípustnou absencí znaku X
(nula až tři možné výskyty). A dále se uplatní V?I{0,3}
přeskočením nepovinného V
a shodou jednoho ze tří možných znaků I
. A pak je tu konec řetězce. No páni.
Pokud jste to všechno stihli sledovat a rozuměli jste tomu napoprvé, jde vám to líp, než to šlo mně. Teď si představte, že se snažíte porozumět regulárnímu výrazu, který napsal někdo jiný a který se nachází uprostřed kritické funkce rozsáhlého programu. Nebo si představte, že se po několika měsících vracíte ke svému vlastnímu regulárnímu výrazu. Už se mi to stalo a není to pěkný pohled.
Podívejme se na alternativní syntaxi, která nám pomůže zapsat regulární výraz tak, aby se dal udržovat.
⁂
Zatím jsme se zabývali tím, čemu budu říkat „kompaktní“ regulární výrazy. Jak jste sami viděli, obtížně se čtou. Dokonce i když přijdete na to, co nějaký z nich dělá, není tu žádná záruka, že mu budete rozumět o šest měsíců později. To, co opravdu potřebujeme, je dokumentace připisovaná k danému místu.
V Pythonu toho lze dosáhnout u takzvaných víceslovných regulárních výrazů (verbose regular expressions). Víceslovný regulární výraz se od kompaktního regulárního výrazu liší ve dvou směrech:
#
a pokračují do konce řádku. V tomto případě jde o komentář uvnitř víceřádkového řetězce a ne uvnitř zdrojového souboru. Ale funguje stejně.
Z dalšího příkladu to bude jasnější. Revidujme kompaktní regulární výraz, s kterým jsme pracovali před chvílí, a převeďme jej na víceslovný regulární výraz. Příklad nám ukáže, jak na to.
>>> pattern = ''' ^ # začátek řetězce M{0,3} # tisíce - 0 až 3 M (CM|CD|D?C{0,3}) # stovky - 900 (CM), 400 (CD), 0-300 (0 až 3 C), # nebo 500-800 (D následované 0 až 3 C) (XC|XL|L?X{0,3}) # desítky - 90 (XC), 40 (XL), 0-30 (0 až 3 X), # nebo 50-80 (L následované 0 až 3 X) (IX|IV|V?I{0,3}) # jednotky - 9 (IX), 4 (IV), 0-3 (0 až 3 I), # nebo 5-8 (V následované 0 až 3 I) $ # konec řetězce ''' >>> re.search(pattern, 'M', re.VERBOSE) ① <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, 'MCMLXXXIX', re.VERBOSE) ② <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, 'MMMDCCCLXXXVIII', re.VERBOSE) ③ <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, 'M') ④
re
je definována konstanta re.VERBOSE
, kterou dáváme najevo, že vzorek se má brát jako víceslovný regulární výraz. Jak vidíte, v tomto vzorku se nachází docela hodně bílých znaků (všechny se ignorují) a několik komentářů (opět se všechny ignorují). Pokud budete ignorovat bílé znaky a komentáře, dostanete naprosto stejný regulární výraz, jaký jsme si ukázali v minulé podkapitole. Ale je mnohem čitelnější.
M
, pak s CM
, následuje L
a tři ze tří možných X
, pak IX
a konec řetězce.
M
, následuje D
a tři ze tří možných C
, pak L
a tři ze tří možných X
, pak V
a tři ze tří možných I
a konec řetězce.
re.VERBOSE
. Takže funkce re.search
považuje vzorek za kompaktní regulární výraz, ve kterém hrají roli všechny bílé znaky i znaky #. Python nemůže rozpoznávat automaticky, zda je regulární výraz víceslovný nebo ne. Python považuje každý regulární výraz za kompaktní — pokud explicitně neřekneme, že je víceslovný.
⁂
Prozatím jsme se soustředili na shodu celých vzorků. Vzorek buď pasuje, nebo ne. Ale regulární výrazy jsou ještě mnohem mocnější. Pokud regulární výraz pasuje, můžeme z řetězce vybrat specifické úseky. Můžeme zjistit, jaká část a kde pasovala.
Následující příklad přinesl opět reálný život. Setkal jsem se s ním o jeden pracovní den dříve než s tím předchozím. Problém: rozklad amerického telefonního čísla. Klient požadoval, aby se číslo dalo zadávat ve volném tvaru (v jednom poli formuláře), ale pak je chtěl mít ve firemní databázi rozdělené na kód oblasti, hlavní linku, číslo a případně klapku. Proštrachal jsem web a našel jsem spoustu příkladů regulárních výrazů, které byly pro tento účel vytvořeny. Ale žádný z nich nebyl dost benevolentní.
Tady máme pár telefonních čísel, která měla být přijata:
800-555-1212
800 555 1212
800.555.1212
(800) 555-1212
1-800-555-1212
800-555-1212-1234
800-555-1212x1234
800-555-1212 ext. 1234
work 1-(800) 555.1212 #1234
Docela široký záběr, že? V každém z těchto případů jsem potřeboval zjistit, že číslo oblasti bylo 800
, číslo hlavní linky bylo 555
a zbytek telefonního čísla byl 1212
. U čísel s klapkou (extension, ext.) jsem potřeboval zjistit, že klapka byla 1234
.
Takže si projděme vývoj řešení pro analýzu telefonního čísla. Následující příklad ukazuje první krok.
>>> phonePattern = re.compile(r'^(\d{3})-(\d{3})-(\d{4})$') ① >>> phonePattern.search('800-555-1212').groups() ② ('800', '555', '1212') >>> phonePattern.search('800-555-1212-1234') ③ >>> phonePattern.search('800-555-1212-1234').groups() ④ Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'NoneType' object has no attribute 'groups'
(\d{3})
. Co to je \d{3}
? No, \d
vyjadřuje „libovolnou číslici (0
až 9
). Společně s {3}
znamená „přesně tři číslice“. Jde o variaci na syntaxi {n,m}
, kterou jsme si ukazovali dříve. Když to vše obalíme do závorek, znamená to „napasuj se přesně na tři číslice a potom si je zapamatuj jako skupinu, kterou si můžeme vyžádat později“. Pak musí následovat pomlčka. Pak má následovat skupina zase přesně tří číslic. A pak další pomlčka. A další skupina tentokrát čtyř číslic. A poté se očekává konec řetězce.
groups()
objektu, který vrátila metoda search()
. Vrací tolikačlennou n-tici, kolik skupin bylo v regulárním výrazu definováno. V našem případě jsme definovali tři skupiny: jednu s třemi číslicemi, další s třemi číslicemi a poslední se čtyřmi číslicemi.
search()
a groups()
. Pokud metoda search()
nevrátí žádnou shodu, vrací None
a nikoliv objekt vyjadřující shodu s regulárním výrazem (MatchObject). Volání None.groups()
vyvolá naprosto zřejmou výjimku. None
totiž žádnou metodu groups()
nemá. (Je to samozřejmě méně zjevné v situaci, kdy se taková výjimka vynoří někde z hloubky našeho kódu. Ano, tady mluvím z vlastní zkušenosti.)
>>> phonePattern = re.compile(r'^(\d{3})-(\d{3})-(\d{4})-(\d+)$') ① >>> phonePattern.search('800-555-1212-1234').groups() ② ('800', '555', '1212', '1234') >>> phonePattern.search('800 555 1212 1234') ③ >>> >>> phonePattern.search('800-555-1212') ④ >>>
groups()
teď vrací n-tici se čtyřmi prvky, protože regulární výraz nyní definuje čtyři pamatované skupiny.
Následující příklad ukazuje regulární výraz, který si poradí s různými oddělovači mezi částmi telefonního čísla.
>>> phonePattern = re.compile(r'^(\d{3})\D+(\d{3})\D+(\d{4})\D+(\d+)$') ① >>> phonePattern.search('800 555 1212 1234').groups() ② ('800', '555', '1212', '1234') >>> phonePattern.search('800-555-1212-1234').groups() ③ ('800', '555', '1212', '1234') >>> phonePattern.search('80055512121234') ④ >>> >>> phonePattern.search('800-555-1212') ⑤ >>>
\D+
. A co je zase tohle? Zápis \D
vyjadřuje libovolný znak s výjimkou číslice a +
znamená „1 nebo víckrát“. Takže \D+
pasuje na jeden nebo více znaků, které nejsou číslicemi. A to je právě to, co použijeme místo přímo zapsané pomlčky a co nám bude pasovat s různými oddělovači.
\D+
místo -
, bude nám regulární výraz pasovat i na telefonní čísla, kde jsou jednotlivé části odděleny mezerami.
Následující příklad ukazuje regulární výraz pro telefonní čísla bez oddělovačů.
>>> phonePattern = re.compile(r'^(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$') ① >>> phonePattern.search('80055512121234').groups() ② ('800', '555', '1212', '1234') >>> phonePattern.search('800.555.1212 x1234').groups() ③ ('800', '555', '1212', '1234') >>> phonePattern.search('800-555-1212').groups() ④ ('800', '555', '1212', '') >>> phonePattern.search('(800)5551212 x1234') ⑤ >>>
+
za *
. Mezi částmi telefonního čísla nyní místo \D+
předepisujeme \D*
. Pamatujete si ještě, že +
znamená „jednou nebo víckrát“? Fajn. Takže *
znamená „nula nebo více výskytů“. Takže teď bychom měli být schopni zpracovat čísla, která neobsahují vůbec žádný oddělovací znak.
800
), potom nula nenumerických znaků, pak následuje zapamatovaná skupina tří číslic (555
), pak nula nenumerických znaků, pak zapamatovaná skupina čtyř číslic (1212
), pak nula nenumerických znaků, pak zapamatovaná skupina libovolného počtu číslic (1234
) a konec řetězce.
x
před klapkou.
groups()
vrací n-tici se čtyřmi prvky i tehdy, když nebyla nalezena klapka. V takovém případě se ale na místě čtvrtého prvku vrací prázdný řetězec.
Další příklad ukazuje, jak bychom si měli počínat.
>>> phonePattern = re.compile(r'^\D*(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$') ① >>> phonePattern.search('(800)5551212 ext. 1234').groups() ② ('800', '555', '1212', '1234') >>> phonePattern.search('800-555-1212').groups() ③ ('800', '555', '1212', '') >>> phonePattern.search('work 1-(800) 555.1212 #1234') ④ >>>
\D*
nula nebo více nenumerických znaků. Všimněte si, že si tyto nenumerické znaky nepamatujeme (předpis není uzavřen v závorkách). Pokud jsou nějaké nalezeny, jednoduše je přeskočíme a teprve pak si zapamatujeme nalezené číslo oblasti.
\D*
nacházející se za první pamatovanou skupinou.)
800
), pak jeden nenumerický znak (pomlčka), zapamatovaná skupina tří číslic (555
), pak jeden nenumerický znak (pomlčka), poté zapamatovaná skupina čtyř číslic (1212
), pak nula nenumerických znaků, pak zapamatovaná skupina nula číslic a na závěr konec řetězce.
1
, ale my jsme předpokládali, že všechny znaky před kódem oblasti budou nenumerické (\D*
). Grrrrr.
Podívejme se na to znovu. Zatím se všechny regulární výrazy chytaly na začátek řetězce. Ale teď vidíme, že se na začátku řetězce může vyskytnout obsah neurčité délky, který bychom chtěli ignorovat. Mohli bychom se sice pokusit o vytvoření předpisu, kterým bychom ten začátek přeskočili, ale zkusme k tomu přistoupit jinak. Nebudeme se vůbec snažit o to, abychom se napasovali na začátek řetězce. Zmíněný přístup je použit v následujícím příkladu.
>>> phonePattern = re.compile(r'(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$') ① >>> phonePattern.search('work 1-(800) 555.1212 #1234').groups() ② ('800', '555', '1212', '1234') >>> phonePattern.search('800-555-1212').groups() ③ ('800', '555', '1212', '') >>> phonePattern.search('80055512121234').groups() ④ ('800', '555', '1212', '1234')
^
. Už se nesnažíme ukotvit na začátek řetězce. Nikde není řečeno, že by se náš regulární výraz měl napasovat na celý vstupní řetězec. Mechanismus, který regulární výraz vyhodnocuje, už si dá tu práci, aby zjistil, od jakého místa vstupního řetězce dochází ke shodě s předpisem, a bude pokračovat odtud.
Vidíte, jak se může regulární výraz rychle vymknout kontrole? Letmo mrkněte na libovolný z předchozích pokusů. Poznáte snadno rozdíl mezi ním a po něm následujícím?
Takže dokud ještě rozumíme konečnému řešení (a tohle opravdu je konečné řešení; pokud jste objevili případ, který by to nezvládlo, nechci o něm vědět), zapišme ho jako víceslovný regulární výraz. Mohli bychom brzy zapomenout, proč jsme něco zapsali právě takto.
>>> phonePattern = re.compile(r''' # nevázat se na začátek řetězce, číslo může začít kdekoliv (\d{3}) # číslo oblasti má 3 číslice (např. '800') \D* # nepovinný oddělovač - libovolný počet nenumerických znaků (\d{3}) # číslo hlavní linky má 3 číslice (např. '555') \D* # nepovinný oddělovač (\d{4}) # zbytek čísla má 4 číslice (např. '1212') \D* # nepovinný oddělovač (\d*) # nepovinná klapka - libovolný počet číslic $ # konec řetězce ''', re.VERBOSE) >>> phonePattern.search('work 1-(800) 555.1212 #1234').groups() ① ('800', '555', '1212', '1234') >>> phonePattern.search('800-555-1212') ② ('800', '555', '1212', '')
⁂
Zatím jsme viděli pouhou špičku ledovce z toho, co regulární výrazy zvládnou. Jinými slovy, ačkoliv jimi můžete být momentálně zcela ohromeni, zatím jste neviděli nic. To mi věřte.
Následující věci už by vám neměly být cizí:
^
odpovídá začátku řetězce.
$
vyjadřuje konec řetězce.
\b
odpovídá hranici slova (word boundary).
\d
odpovídá číslici.
\D
odpovídá znaku jinému než číslice.
x?
odpovídá nepovinnému znaku x
(jinými slovy vyjadřuje žádný nebo jeden výskyt x
).
x*
vyjadřuje nula nebo více výskytů x
.
x+
odpovídá x
jedenkrát nebo víckrát.
x{n,m}
vyjadřuje znak x
opakovaný nejméně n
-krát, ale ne více než m
-krát.
(a|b|c)
odpovídá přesně jedné z možností a
, b
nebo c
.
(x)
vyjadřuje obecně zapamatovanou skupinu. Hodnotu zapamatované skupiny můžeme získat voláním metody groups()
objektu, který byl vrácen voláním re.search
.
Regulární výrazy jsou velmi mocné, ale jejich použití není správným řešením pro každý problém. Měli byste se o nich naučit tolik, abyste věděli, kdy je jejich použití vhodné, kdy vám pomohou problém vyřešit a kdy naopak způsobí víc problémů, než vyřeší.
© 2001–11 Mark Pilgrim