A Python programozási nyelv

Reguláris kifejezések

A reguláris kifejezés fogalma nem Python-függő nyelvi elem, hanem sztringek egy halmazának egyszerű leírására szolgáló eszköz, mely sokoldalúsága és bonyolultsága miatt itt nem kerül bemutatásra. Ha a reguláris kifejezések megismerésének vágya fűt minket, akkor olvassuk el a "Regular Expression HOWTO"-t, ami elérhető a http://www.python.org/doc/howto/ oldalról. Itt most azonban csak a reguláris kifejezések Pythonban való használatára koncentrálunk.

Reguláris kifejezéseket a re modul importálásával használhatunk, az általa biztosított függvények segítségével. A jelenlegi verzió dokumentációja kiemeli, hogy az új motor képes Unicode sztringek kezelésére mind a szövegben, mind a mintában, ám még tartalmazhat hibákat bonyolultabb reguláris kifejezéseknél, szemben a régi, Unicode sztringeket nem kezelő, de alaposan tesztelt változattal. ígérik, hogy a talált hibákat javítani fogják, ezért szerintem nyugodtan használhatjuk az új modult.

Reguláris kifejezéseket célszerű nyers sztringekként megadni, mert ezek amúgy sem tartoznak a könnyed olvasmányok közé :-).
Két függvényt is használhatunk az illesztésre, ami egy apró ám nagyon lényeges dologban tér el egymástól. A két függvény a match() és a search(). Ha valaki például Perlben szokott reguláris kifejezésekhez, akkor az ottani szemantikának a search() felel meg.
Az match() ugyanis csak akkor sikeres, ha a megadott sztringre a minta a sztring elején (illetve az opcionálisan megadott kezdőpozíciónál illeszkedik). Ezért van az, hogy a következő példában az első hívás alapján az "a" minta nem illeszkedik a "ba" sztingre.

>>> if re.compile("a").match("ba"):
...     print "MATCH"
... else
...     print "NO MATCH"
...
NO MATCH
>>> if re.compile("a").match("ba", 1):
...     print "MATCH"
... else:
...     print "NO MATCH"
...
MATCH
>>> if re.compile("a").search("ba"):
...     print "MATCH"
... else:
...     print "NO MATCH"
...
MATCH
>>>

A Python objektum-orientált szemlélete miatt a minták, és az eredmények is osztályként vannak definiálva, és ezek példányai (regular expression objects, match objects) használhatók a mintaillesztés eredményének kezelésére, szemben például a Perllel, ahol nyelvi elemként használhatjuk őket. Egy reguláris kifejezést reprezentáló objektumra a re modul compile() tehetünk szert. Ez a függvény első paraméterében egy mintát vesz át, a másodikban van lehetőségünk különböző flag-ek beállítására, amik a szokásos módosításokat lehetővé teszik (ignorecase-i, multiline-m, dotall-s, verbose-x), és a szókaraktereket meghatározzák (locale-l, unicode-u). A további műveleteket ezen az objektumon végezhetjük. A modul sok lehetőséget kínál ezen műveletek egyszerűsítésére például azzal, hogy ad egy match(pattern, string) függvényt, ami először a compile műveletet végzi el, aztán az illesztést, és visszakapjuk a match objektumot további használatra (illetve None-t, ha nem illeszkedett a minta). Ehhez hasonló függvényeket itt nem részletezünk, megtalálhatóak a modul dokumentációjában.

A search() és a match() metódusok közötti különbséget már fentebb megvilágítottuk egy példával. Lehetőség van rá, hogy egy sztringet egy bizonyos minta mentén feldarabdoljunk a split() metódussal. Minden illeszkedő részsztringet megkeres a findall() metódus, és az eredményt egy listában kapjuk vissza.

>>> regexp = re.compile(r'\ba\w+a\b', re.IGNORECASE | re.LOCALE)
>>> regexp.findall('Attila egy Ankara melletti faluban alma evést követően egy
ananászfa mellett alva pihen.')
['Attila', 'Ankara', 'alma', 'anan\xe1szfa', 'alva']
>>>

A sub(cseresztring, eredeti_sztring[, max_csere]) és a subn(cseresztring, eredeti_sztring[, max_csere]) függvények cserékre adnak lehetőséget. A kettő között csupán annyi a különbség, hogy az első visszatérési értéke az eredmény sztring, a másodiké pedig egy tuple: (eredmény_sztring, cserék_száma). Az előző példát folytatva például:

>>> regexp.sub('A...A', 'Attila egy Ankara melletti faluban alma evést követően
egy ananászfa mellett alva pihen.')
'A...A egy A...A melletti faluban A...A ev\xe9st k\xf6vet\xf5en egy A...A
mellett A...A pihen.'
>>> regexp.subn('A...A', 'Attila egy Ankara melletti faluban alma evést követően
egy ananászfa mellett alva pihen.')
('A...A egy A...A melletti faluban A...A ev\xe9st k\xf6vet\xf5en egy A...A
mellett A...A pihen.', 5)
>>>

Érdekesség Pythonban az, hogy egy minta megadásakor nem csak a szokásos módon hivatkozhatunk a csoportosított karakterekre (azaz \1, \2, ..., \10, \11, ...), hanem nevesíthetjük ezeket a csoportokat, ezekre a nevesített csoportokra a mintán, a cseresztringen belül és a match objektumon keresztül is hivatkozhatunka nevüket használva. Ennek előnye az, hogy ha módosítjuk a mintát, akkor egy új csoport beszúrásakor nem okoz gondot, hogy minden csoport sorszáma eggyel eltolódik, ha névvel, és nem számmal hivatkozunk rá.
A következő példa megmutatja, hogy használhatjuk a nevesített csoportokat a mintáinkban. Csoport elnevezése a (?P<csoport_név>) formában adható meg. Még ugyanabban a mintában is hivatkozhatunk erre a csoportra (?P=csoport_név) formában, erre példa a color nevű csoport használata. A cseresztringben más módon, \g<csoport_név> formában hivatkozhatunk egy nevesített csoportra.
A következő példa egy olyan HTML forrásban cserélget óvatosan, ahol felkészültünk arra, hogy az oldalunknak két különböző színezése is lehet, és HTML megjegyzésként megadtuk minden font tag után, hogy milyen színt milyenre szeretnénk cserélni. A példa sztringben a piros (red) színt szeretnénk feketére (black) cserélni.
Vegyük észre, hogy a re.compile() függvényt egy paraméterrel hívjuk, és csak az olvashatóság miatt 4 soros a függvényhívás! A két sztringet, mint két közvetlenül egymás mellett álló sztring literált a Python automatikusan konkatenálja (szintaktikai cukor).

>>> fonts = re.compile(
... '(?P<before><font [^>]*)color="(?P<color>.+?)"(?P<after>.*?>)'
... '<!-- replace (?P=color) with (?P<new_color>.*?)-->'
... )
>>> fonts.sub(
... r'\g<before>color="\g<new_color>"\g<after>',
... '<font face="Verdana" color="red" size="2"><!-- replace red with black-->'
... )
'<font face="Verdana" color="black" size="2">'
>>>

A nevesítés ellenére is hivatkozhatnánk sorszámokkal a csoportokra, de ez nehezebben olvasható:

>>> fonts.sub(
... r'\1color="\4"\3',
... '<font face="Verdana" color="red" size="2"><!-- replace red with black-->'
... )
'<font face="Verdana" color="black" size="2">'
>>>

A match objektumok az illesztés eredményét teszik elérhetővé. Elérhetőek a csoportok, megtudhatjuk, hogy az eredeti sztring melyik karakterén kezdődik a csoport, és hol ér véget (span()), a csoport megadásánál pedig mind névvel, mind pedig a csoport indexével hivatkozhatunk. Az előző példát folytatva:

>>> m = fonts.search(
... '<font face="Verdana" color="red" size="2"><!-- replace red with black-->'
... )
>>> m
<SRE_Match object at 0x8134020>
>>> m.groupdict()
{'before': '<font face="Verdana" ', 'new_color': 'black', 'after': ' size="2">',
'color': 'red'}
>>> m.group('color')
'red'
>>> m.span('color')
(28, 31)
>>> m.span(2)
(28, 31)

Az expand metódussal lehetőségünk van arra, hogy a sztringben megtalált csoportokra hivatkozva template sztringekbe beillesszük a csoport által megadott részsztringeket. Például:

>>> m.expand(r'A "\g<color>" színt cseréltük "\g<new_color>" színre.')
'A "red" sz\xednt cser\xe9lt\xfck "black" sz\xednre.'
>>>