Regexpy uvnitř Python'u můžeme použít v zásadě ke dvěma rozdílným operacím:
Poznámka: Následující slajdy předpokládají alespoň základní znalost regexpů. Pro rychlé zopakování či seznámení se se základními principy viz tuto přednášku.
Regexpové nástroje jsou v Python'u uschovány v modulu re
. Ten definuje především:
Kromě toho je v něm definována regexpová výjimka re.error a další podobné záležitosti.
Ač můžeme regepxy používat tak říkajíc „přímo“, mnohem častější (zvláště u opakovaně používaných regexpů) je jejich zavedení v podobě předkompilovaného objektu (tzv. pattern object), operace na němž jsou rychlejší (právě proto, že řetězcový popis regexpu byl již převeden do interní podoby uzpůsobené vyhledávání vzorů).
Ke kompilaci slouží metoda re.compile(). Její použití je následující:
r''
nebo r""
) se vyhneme problémům s vyhodnocováním některých sekvencí znaků (typicky všechny uvozené zpětným lomítkem \
) jiným způsobem, než bychom mohli zrovna potřebovat.
|
binárního or. Každá z možných konstant totiž nastavuje svůj příslušný jeden bit na celkovém kompilačním příznaku.
Na takto zavedeném objektu pattern máme k dispozici celou armádu regexpových metod. S vybranými se seznámíme na následujících slajdech.
Nejpřirozenějším python-trojkovským způsobem, jak v zadaném řetězci vyhledat všechny (nepřekrývající se) výskyty podřetězce zadaného daným regexpem, je použít metodu finditer(). Najde-li tato vyhovující zásahy (v podobě match-objektů), vrátí nad nimi iterátor, takže je můžeme snadno prozkoumat například pomocí smyčky for-in:
finditer(string, pos, endpos)
, kde nepovinný druhý a třetí parametr určují, odkud až kam bude řetězec prohledáván.
Návratové match-objekty z metody finditer() mají svoji poměrně složitou vnitřní strukturu. Podívejme se jim trochu na zoubek:
Tři použité metody mají zjevně následující význam:
Poznámka: Objekt zásahu má vlastností daleko více, než jsme si zde ukázali.
Na první pohled se funkce findall() tváří jako klasická neiterátorová varianta k funkci finditer(), která prostě mímo iterátoru vrací starý klasický seznam všech zásahů. Jenže to není tak úplně pravda, protože její výstup se liší podle toho, zda regexp obsahuje nebo neobsahuje podskupiny.
findall(string, pos, endpos)
, kde nepovinný druhý a třetí parametr určují, odkud až kam bude řetězec prohledáván.
Bez podskupin se nám vrátí seznam všech podřetězců vyhovujících danému regexpu:
S podskupinami se nám vrátí seznam n-tic všech nalezených podskupin z podřetězců vyhovujících danému regexpu:
Pokud nás zajímá pouze první zásah (nebo ekvivalentně zda se daný regexp v řetězci prostě vůbec někde vyskytuje), použijeme nejspíše funkci search():
None
.
search(string, pos, endpos)
, kde nepovinný druhý a třetí parametr určují, odkud až kam bude řetězec prohledáván.
Velmi podobně jako search() se chová metoda match(). Jediným rozdílem oproti search() je, že hledá zásahy pouze od začátku prohledávaného textu, takže pro stejný příklad jako na předchozím slajdu nenajde nic:
match(string, pos, endpos)
, kde nepovinný druhý a třetí parametr určují, odkud až kam bude řetězec prohledáván.
Podívejme se podrobněji na práci s návratovým objektem zásahu. V následujícím příkladu se pokusíme rozložit vstupní adresu na jednotlivé významové prvky:
Vidíme, že máme k dispozici standardní přehledy zásahů group() a groups() a navíc, jelikož jsme v regexpu zavedli pojmenované skupiny, máme k dispozici i jejich velmi šikovný slovník:
groupdict()
– slovník jmen pojmenovaných skupin jako klíčů a jim odpovídajících podřetězců jako hodnot
Poznámka: Metody groups() a groupdict() se dají zavolat s jedním nepovinným parametrem default. Ten se uplatní v případě, že některá z hledaných skupin se v řetězci nevyskytla – bez jeho uvedení bude taková skupina ve výpise nahrazena hodnotou None
, s jeho uvedením právě jeho hodnotou.
Zůstaňme ještě chvíli u předchozího příkladu a podívejme se více na metodu group():
Zjevně platí:
group()
zavolaná bez parametru je totéž, jako volání group(0)
. Přitom vrácenou hodnotou je řetězec zachycený celým regexpem.
group(1)
), resp. odpovídajícího jména (např. group('host')
), zavedli-li jsme skupiny jako pojmenované, nám metoda vrátí pouze odpovídající řetězec zásahu.
Poznámka: Nebyla-li příslušná skupina zachycena, vrátí se místo ní None
. Pokus o získání neexistující skupiny vyvolá výjimku IndexError
.
Podobně jako řetězcová metoda split() i regexpová varianta slouží k rozdělení řetězce na místech určených svým argumentem. Jen pochopitelně pro regexpovou metodu je argumentem regexp, takže je mnohem flexibilnější a mocnější.
split(string, maxsplit=0)
, kde druhý parametr určuje, ke kolika nejvýše rozdělením má dojít.
Podobně jako funkce findall() se její návratové hodnoty poněkud liší podle toho, obsahuje-li regexp podskupiny nebo ne.
Bez podskupin se nám vrátí seznam podřetězců oddělených podle částí vyhovujících danému regexpu:
S podskupinami se nám vrátí stejný seznam, ale navíc včetně těchto oddělovačů:
Podobně jako u předchozí metody split() je metoda sub() regexpovým zobecněním řetězcových metod replace() a translate(). Jinými slovy – kde nebudou pro záměnu stačit (podstatně rychlejší) řetězcové metody se svými pevnými vstupními argumenty, tam použijeme sub() s regexpovým vstupem:
XXX
.
sub(repl, string, count=0)
, kde nepovinný parametr count určuje nejvyšší počet výměn, ke kterým může dojít.
V nahrazovaném textu můžete použít i reference na pojmenované skupiny, a to ve tvaru \g<JMÉNO>
:
U reference podle čísel skupin je tvar stejný – také dlouze \g<ČÍSLO>
:
\g<2>0
, které by ve tvaru \20
hledaly skupinu číslo 20 (a ne požadovanou 2).
Úplně totéž jako sub() zařídí i metoda subn(), pouze spolu s pozměněným řetězcem vrátí v podobě dvojce i počet záměn, ke kterým došlo:
subn(repl, string, count=0)
, kde nepovinný parametr count určuje nejvyšší počet výměn, ke kterým může dojít.
Jelikož regexpy se mohou velmi snadno stát značně složitými a zcela nepřehlednými, vyplatí se vědět, jak si v takovém případě poradit. Srovnejme klasický způsob parsování numerické entity z XML..
..se dvěma alternativními možnými zápisy jinak úplně stejného regexpu.
Předně si můžete rozložit složitější regexp na logické podčásti a využít pythoního triku s automatickým skládáním řetězců:
Druhou možností pak je využít přímo regexpí možnost zápisu pomocí uvolněné syntaxe (aka verbose mode). V tomto módu jsou totiž ignorovány všechny bílé znaky, pokud nejsou součástí skupiny znaků []
nebo pokud nejsou uvozeny zpětným lomítkem (jehož speciální význam ovšem není zrovna dalším zpětným lomítkem zrušen), plus se v něm dají psát klasické komentáře pomocí znaku #
:
Z praktických důvodů se dají všechny výše uvedené metody zavolat nikoli až na zkompilovaném objektu vzorů, nýbrž už přímo na úrovni modulu re. Pochopitelně jim pak ještě musíme nějak předhodit samotný regexp a případné kompilační příznaky. Děje se tak v podobě prvního parametru pro regexp a posledního pojmenovaného (flags) pro příznaky:
re.finditer(pattern, string, flags=0)
re.findall(pattern, string, flags=0)
re.search(pattern, string, flags=0)
re.match(pattern, string, flags=0)
re.split(pattern, string, maxsplit=0, flags=0)
re.sub(pattern, repl, string, count=0, flags=0)
re.subn(pattern, repl, string, count=0, flags=0)
Někdy můžete potřebovat najít skutečně všechny výskyty nějakého vzoru, a to i když se ve zdrojovém textu překrývají. Jako například pětipísmenný podřetězec začínající na znaky abc
v řetězci 'abcabc12345'
. Obě odpovědi jsou jasné – 'abcab'
a 'abc12'
. Nicméně knihovní modul re si to nemyslí:
Myšlenkově nejsnažší je použít alternativní regexpový modul regex, který na svých metodách findall() a finditer() podporuje přepínač pro vyhledávání překrývajících se vzorů:
Jste-li omezeni na standardní knihovnu, můžete využít triku s vyhlížením:
r'
a (?=
) a prázdné množiny se technicky vzato nepřekrývají, takže poslušně najde všechny výskyty vyhlížecího vzoru, který je označen jako skupina a probublá tudíž do výsledných zásahů.
Vidíme, že regexpy jsou velmi mocný nástroj pro práci s řetězci. Ale jak řekl klasik:
Programátor vidí problém a řekne si: „Ha, na to použiji regexpy!“
V tu chvíli má problémy dva.
Jinak řečeno: Rozumíte-li regexpům a umíte-li je, jako když bičem mrská, směle do nich. Není-li tomu tak, připravte se na spoustu frustrujících okamžiků, kdy zdánlivě zcela jasný regexp bude dělat něco úplně jiného, než jste od něj čekali ^_^