Itt vagy: Kezdőlap ‣ Ugorj fejest a Python 3-ba ‣
Nehézségi szint: ♦♦♦♢♢
❝ Egyesek amikor találkoznak egy problémával, azt gondolják: „Tudom, reguláris kifejezéseket fogok használni.” Innentől két problémájuk van. ❞
– Jamie Zawinski
Egy kisebb szövegdarab kinyerése egy nagy szövegblokkból mindig kihívást jelent. Pythonban a karakterláncok rendelkeznek a keresésre és cserére szolgáló metódusokkal: index()
, find()
, split()
, count()
, replace()
stb. De ezek a metódusok a legegyszerűbb esetekre vannak korlátozva. Az index()
metódus például egyetlen bedrótozott részkarakterláncot keres, és a keresés mindig megkülönbözteti a kis- és nagybetűket. A kis- és nagybetűket meg nem különböztető kereséshez az s karaktersorozatban, meg kell hívnod az s.lower()
vagy s.upper()
metódust, és meg kell győződnöd, hogy a keresőkifejezések ennek megfelelően kis- vagy nagybetűsek. A replace()
és split()
metódusok ugyanilyen korlátozásokkal rendelkeznek.
Ha a célod elérhető karakterlánc-metódusokkal, akkor használd azokat. Ezek gyorsak, egyszerűek és könnyen olvashatók, a gyors, egyszerű és könnyen olvasható kódról pedig rengeteget lehetne beszélni. De ha azt veszed észre, hogy rengeteg különböző karakterláncfüggvényt használsz if
utasításokkal a speciális esetek kezelésére, vagy a split()
és join()
hívások láncolataival aprítod a karakterláncaid, akkor szükséges lehet a reguláris kifejezésekre váltás.
A reguláris kifejezések hatékony és (nagyrészt) szabványosított lehetőséget adnak bonyolult karaktermintákat tartalmazó szövegek keresésére, cseréjére és feldolgozására. Ugyanakkor a reguláris kifejezések szintaxisa szoros, és a normál kóddal ellentétben az eredmény olvashatóbb lehet, mint a karakterláncfüggvények hosszú láncolatát használó barkácsmegoldás. A reguláris kifejezéseken belül még megjegyzések is elhelyezhetők, így részletes dokumentációt mellékelhetsz.
☞Ha használtál reguláris kifejezéseket más nyelveken (mint a Perl, JavaScript vagy PHP), akkor a Python szintaxisa nagyon ismerős lesz. Olvasd el a
re
modul összefoglalását az elérhető függvények és azok paramétereinek áttekintéséhez.
⁂
Ezen példák sorozatát egy valós életbeli probléma inspirálta, amellyel napi munkám során találkoztam évekkel ezelőtt, amikor egy örökölt rendszerből származó lakcímeket kellett kitisztítanom és szabványosítanom, mielőtt egy újabb rendszerbe importálhattam volna azokat. (Látod, ezeket a dolgokat nem csak úgy kitalálom, ez tényleg hasznos.) Ez a példa bemutatja, hogyan közelítettem meg a problémát.
>>> 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'
mindig 'RD.'
-ként legyen rövidítve. Első ránézésre ez elég egyszerűnek tűnt ahhoz, hogy a replace()
karakterlánc-metódust használjam. Végül is minden adat már nagybetűs volt, így a kis- és nagybetűk eltérése nem okozhatott gondot. És a keresett kifejezés, a 'ROAD'
konstans volt. És ebben a megtévesztően egyszerű példában az s.replace()
tényleg működik.
'ROAD'
kétszer jelenik meg a címben, egyszer a 'BROAD'
utcanév részeként, egyszer pedig önálló szóként. A replace()
metódus mindkét előfordulást látja, és vakon kicseréli mindkettőt, emiatt a címek szépen megsemmisülnek.
'ROAD'
rész-karakterláncot tartalmazó címek problémájának megoldásához próbálkozhatsz valami ilyesmivel: a 'ROAD'
szót csak a cím utolsó négy karakterében (s[-4:]
) keresed és cseréled, a karakterlánc többi részét (s[:-4]
) pedig békén hagyod. De az már most látszik, hogy ez így nyögvenyelős lesz. A minta például függ a lecserélt karakterlánc hosszától. (Ha a 'STREET'
szót cserélnéd az 'ST.'
-re, akkor a s[:-6]
és s[-6:].replace(...)
hívásokat kellene használnod.) Szeretnél hat hónap múlva visszatérni ehhez, és hibákat keresni benne? Azt tudom, hogy én biztosan nem.
re
modulban van.
'ROAD$'
. Ez egy egyszerű reguláris kifejezés, amely csak akkor illeszkedik a'ROAD'
szóra, ha az a karakterlánc végén van. A $
a „karakterlánc végét” jelenti. (Ennek párja a ^
, amely a „karakterlánc elejét” jelzi.) A re.sub()
függvény használatakor az s karakterláncban a 'ROAD$'
reguláris kifejezést keresed, és helyettesíted az'RD.'
-vel. Ez illeszkedik az s karakterlánc végén lévő ROAD
előfordulásra, de nem illeszkedik a BROAD
szóban lévő ROAD
előfordulásra, mert ez az s közepén van.
A címtisztítós történetet folytatva, hamarosan észrevettem, hogy az előző példa, a cím végére illeszkedő 'ROAD'
nem elég jó, mert nem minden címben van az utca megjelölve. Néhány cím egyszerűen csak az utca nevével végződik. Legtöbbször nem okozott gondot, de ha az utcanév a 'BROAD'
volt, akkor a reguláris kifejezés illeszkedett volna a karakterlánc végén lévő, a 'BROAD'
részét képező 'ROAD'
szövegre, de én nem ezt akartam.
>>> 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'
előfordulások megtalálása, amelyek a karakterlánc végén és önálló szóként voltak jelen (nem pedig egy hosszabb szó részeiként). Ezt reguláris kifejezésként a \b
használatával lehet kifejezni, amely azt jelenti, hogy „pontosan itt szóhatárnak kell lennie”. Pythonban ezt az a tény bonyolítja, hogy a karakterláncban lévő '\'
karaktert escape-elni kell. Ezt néha backslash járványnak hívják, és az egyik oka annak, hogy a reguláris kifejezések egyszerűbbek Perlben, mint Pythonban. A hátulütő ott van, hogy a Perl a reguláris kifejezéseket más szintaktikai elemekkel keveri, így hiba esetén nehéz megállapítani, hogy az a szintaktikai elemek vagy a reguláris kifejezés hibája-e.
r
beszúrásával. Ezzel megmondod a Pythonnak, hogy a karakterláncban semmit nem kell escape-elni: a '\t'
egy tab karakter, de az r'\t'
valójában egy fordított törtvonal karakter (\
), amelyet a t
betű követ. Reguláris kifejezések használatakor mindig a nyers karakterláncok használatát javaslom; ellenkező esetben a dolgok túl gyorsan válnak túl zavarossá (és a reguláris kifejezések egyébként is elég zavarosak).
'ROAD'
szót önállóan tartalmazta, de nem a végén, mert a cím az utca megadása után az ajtószámot is tartalmazta. Mivel a 'ROAD'
nem a karakterlánc legvégén van, nem illeszkedik, így az egész re.sub()
hívás nem cserél le semmit, és visszakapod az eredeti karakterláncot, de nem ezt akartad.
$
karaktert, és egy újabb \b
-t adtam hozzá. A reguláris kifejezés most ezt jelenti: „illeszkedj a 'ROAD'
szóra, ha az önállóan fordul elő a karakterláncban bárhol”, legyen az a végén, az elején vagy valahol középen.
⁂
A római számokkal gyakran találkozhatsz, még ha nem is veszed mindig a fáradságot a visszafejtésükhöz. Előfordulnak régi filmek és TV-műsorok szerzői jog sorában („Copyright MCMXLVI
” a „Copyright 1946
” helyett), vagy a könyvtárak és egyetemek dedikációs falán („alapítva MDCCCLXXXVIII
” az „alapítva 1888
” helyett). Láthatsz ilyeneket vázlatokban és bibliográfiai hivatkozásokban. Ez a számábrázolási rendszer tényleg az ókori római birodalomban született (ezért ez a neve).
A római számok alatt hét karaktert értünk, amelyek különböző módokon ismétlődve és kombinálódva ábrázolják a számokat.
I = 1
V = 5
X = 10
L = 50
C = 100
D = 500
M = 1000
A következő néhány általános szabály vonatkozik a római számok előállítására:
I
= 1
, az II
= 2
és az III
= 3
. A VI
= 6
(szó szerint „5
és 1
”), a VII
= 7
és a VIII
= 8
.
I
, X
, C
és M
) legfeljebb háromszor ismételhetők. A 4
esetén azt a következő ötös karakterből kell kivonnod. A 4
nem ábrázolható IIII
-ként; ehelyett a IV
használatos („1
-gyel kisebb, mint 5
”). A 40
= XL
(„10
-zel kevesebb, mint 50
”), 41
= XLI
, 42
= XLII
, 43
= XLIII
és a 44
= XLIV
(„10
-zel kevesebb, mint 50
, és 1
-gyel kevesebb, mint 5
”).
9
esetén például a tőle nagyobb legelső tizes karakterből kell kivonnod: a 8
= VIII
, de a 9
= IX
(„1
-gyel kevesebb, mint 10
”), nem VIIII
(mivel az I
karakter nem ismételhető négyszer). A 90
= XC
, a 900
= CM
.
10
mindig X
-ként van ábrázolva, soha nem VV
-ként. A 100
mindig C
, soha nem LL
.
DC
= 600
; a CD
egy teljesen különböző szám (400
, „100
-zal kevesebb, mint 500
”). A CI
= 101
; az IC
nem is érvényes római szám (mert nem vonhatsz ki 1
-et közvetlenül a 100
-ból; 99 =XCIX
, „10
-zel kisebb, mint 100
, majd 1
-gyel kisebb, mint 10
”).
Mi kellene egy tetszőleges karakterlánc érvényes római szám mivoltának ellenőrzéséhez?Nézzük meg számjegyenként. Mivel a római számok mindig a legnagyobbtól a legkisebb felé íródnak, kezdjük a legmagasabbal: az ezres hellyel. Az 1000 és nagyobb számok esetén az ezreseket M
karakterek sorozata képviseli.
>>> import re >>> minta = '^M?M?M?$' ① >>> re.search(minta, 'M') ② <_sre.SRE_Match object at 0106FB58> >>> re.search(minta, 'MM') ③ <_sre.SRE_Match object at 0106C290> >>> re.search(minta, 'MMM') ④ <_sre.SRE_Match object at 0106AA38> >>> re.search(minta, 'MMMM') ⑤ >>> re.search(minta, '') ⑥ <_sre.SRE_Match object at 0106F4A8>
^
hatására az azt követő elemeket csak a karakterlánc elejére illeszti. Ha ez nem lenne megadva, akkor a minta mindenképp illeszkedne, bárhol is legyenek az M
karakterek, de most nem ezt akarjuk. Arról kell meggyőződni, hogy ha vannak M
karakterek, akkor azok a karakterlánc elején vannak. Az M?
egyetlen elhagyható M
karakterre illeszkedik. Mivel ez háromszor ismétlődik, ez a minta 0 - 3 egymást követő M
karakterre illeszkedik. És a $
a karakterlánc végére illeszkedik. Az elején lévő ^
karakterrel együtt ez azt jelenti, hogy a mintának a teljes karakterláncra kell illeszkednie, az M
karakterek előtt vagy után semmi sem állhat.
re
modul lényege a search()
függvény, amely egy reguláris kifejezést (pattern) és egy karakterláncot ('M'
) vár, amelyre megpróbálja a reguláris kifejezést illeszteni. Ha egyezést talál, akkor a search()
egy objektumot ad vissza, amely különböző metódusokkal rendelkezik a találat leírására; ha nincs találat, akkor a search()
a Python null értékét, a None
objektumot adja vissza. Jelenleg csak azzal foglalkozunk, hogy a minta illeszkedik-e, amit a search()
kimenetéből ránézésre meg lehet állapítani. Az 'M'
illeszkedik a reguláris kifejezésre, mert az első elhagyható M
illeszkedik, a második és harmadik elhagyható M
karakter pedig figyelmen kívül marad.
'MM'
illeszkedik, mert az első és második elhagyható M
karakter illeszkedik, a harmadik M
pedig figyelmen kívül marad.
'MMM'
illeszkedik, mert mind a három M
karakter illeszkedik.
'MMMM'
nem illeszkedik. Mind a három M
karakter illeszkedik, de a reguláris kifejezés ragaszkodik a karakterlánc végéhez, (a $
karakter miatt), ám a karakterláncnak még nincs vége (a negyedik M
miatt). Így a search()
a None
objektumot adja vissza.
M
karakter elhagyható.
A százasok helye bonyolultabb az ezresekénél, mert több egymást kizáró módon fejezhető ki az értékétől függően.
100 = C
200 = CC
300 = CCC
400 = CD
500 = D
600 = DC
700 = DCC
800 = DCCC
900 = CM
Így négy lehetséges minta van:
CM
CD
C
karakter (nulla, ha a százasok helyiértéke 0)
D
, amelyet 0 - 3 C
karakter követ
Az utolsó két minta kombinálható:
D
, amelyet 0 - 3 C
karakter követ
Ez a példa bemutatja, hogyan ellenőrizhető a százas helyiérték római szám mivolta.
>>> import re >>> minta = '^M?M?M?(CM|CD|D?C?C?C?)$' ① >>> re.search(minta, 'MCM') ② <_sre.SRE_Match object at 01070390> >>> re.search(minta, 'MD') ③ <_sre.SRE_Match object at 01073A50> >>> re.search(minta, 'MMMCCC') ④ <_sre.SRE_Match object at 010748A8> >>> re.search(minta, 'MCMC') ⑤ >>> re.search(minta, '') ⑥ <_sre.SRE_Match object at 01071D98>
^
), majd az ezres helyiértéket (M?M?M?
). Ezután következik a zárójelben lévő új rész, amely három, egymást kölcsönösen kizáró minta függőleges vonalakkal elválasztott halmazát adja meg: CM
, CD
és D?C?C?C?
(ez egy elhagyható D
, amelyet 0 - 3 elhagyható C
karakter követ). A reguláris kifejezések feldolgozása sorban (balról jobbra) ellenőrzi ezeket a mintákat, veszi az első illeszkedőt, és figyelmen kívül hagyja a többit.
'MCM'
illeszkedik, mert az első M
illeszkedik, a második és harmadik M
karakterek figyelmen kívül maradnak, és a CM
illeszkedik (így a CD
és D?C?C?C?
minták nem is lesznek figyelembe véve). Az MCM
az 1900
római számokkal leírt változata.
'MD'
illeszkedik, mert az első M
illeszkedik, a második és harmadik M
karakterek figyelmen kívül maradnak, és a D?C?C?C?
minta illeszkedik a D
-re (a három C
karakter mindegyike elhagyható, és figyelmen kívül marad). Az MD
az 1500
római számokkal leírt változata.
'MMMCCC'
illeszkedik, mert mind a három M
karakter illeszkedik, és a D?C?C?C?
minta illeszkedik a CCC
-re (a D
elhagyható és figyelmen kívül marad). Az MMMCCC
a 3300
római számokkal leírt változata.
'MCMC'
nem illeszkedik. Az első M
illeszkedik, a második és harmadik M
karakterek figyelmen kívül maradnak, a CM
illeszkedik, de ezután a $
nem illeszkedik, mert még nincs vége a karakterláncnak (még van egy illeszkedés nélküli C
karakter). A C
nem illeszkedik a D?C?C?C?
minta részeként, mert a kölcsönösen kizáró CM
minta már illeszkedett.
M
karakter elhagyható és figyelmen kívül marad, és az üres karakterlánc illeszkedik a D?C?C?C?
mintára, amelynek minden karaktere elhagyható és figyelmen kívül marad.
Huh! Látod, a reguláris kifejezések milyen gyorsan válnak nagyon bonyolulttá? És még csak a római számok ezres és százas helyiértékeit fedtük le. De ha ezt az egészet sikerült követned, akkor a tizesek és az egyesek könnyűek lesznek, mert pontosan ugyanezt a mintát követik. De nézzünk egy másik módszert a minta kifejezésére.
⁂
{n,m}
szintaxis használataAz előző szakaszban egy olyan mintával foglalkoztunk, amelyben ugyanaz a karakter legfeljebb háromszor ismétlődhetett. Ezt reguláris kifejezésekkel máshogy is ki lehet fejezni, amelyet egyesek olvashatóbbnak ítélnek. Először nézzük meg az előző példában már használt metódust.
>>> import re >>> minta = '^M?M?M?$' >>> re.search(minta, 'M') ① <_sre.SRE_Match object at 0x008EE090> >>> re.search(minta, 'MM') ② <_sre.SRE_Match object at 0x008EEB48> >>> re.search(minta, 'MMM') ③ <_sre.SRE_Match object at 0x008EE090> >>> re.search(minta, 'MMMM') ④ >>>
M
karakterre, de a második és harmadik M
-re nem (de ez nem gond, ezek elhagyhatók), majd a karakterlánc végére.
M
karakterre, de a harmadik M
-re nem (de ez nem gond, ez elhagyható), majd a karakterlánc végére.
M
karakterre, majd a karakterlánc végére.
M
karakterre, de ezután nem illeszkedik a karakterlánc végére (mert még van egy nem illeszkedő M
), így a minta nem illeszkedik és a None
objektumot adja vissza.
>>> minta = '^M{0,3}$' ① >>> re.search(minta, 'M') ② <_sre.SRE_Match object at 0x008EEB48> >>> re.search(minta, 'MM') ③ <_sre.SRE_Match object at 0x008EE090> >>> re.search(minta, 'MMM') ④ <_sre.SRE_Match object at 0x008EEDA8> >>> re.search(minta, 'MMMM') ⑤ >>>
M
karakterre, majd a karakterlánc végére.” A 0 és 3 tetszőleges szám lehet, ha legalább egy és legfeljebb 3 M
karaktert szeretnél illeszteni, akkor ezt mondhatnád:M{1,3}
.
M
karakterből egyre, majd a karakterlánc végére.
M
karakterből kettőre, majd a karakterlánc végére.
M
karakterből háromra, majd a karakterlánc végére.
M
karakterből háromra, de ezután nem illeszkedik a karakterlánc végére. A reguláris kifejezés legfeljebb csak három M
karaktert engedélyez a karakterlánc vége előtt, de itt négy van, így a minta nem illeszkedik, és a None
objektumot adja vissza.
Bővítsük ki a római számos reguláris kifejezést a tizes és egyes helyiértékekkel. Ez a példa bemutatja a tizes helyiértékek ellenőrzését.
>>> minta = '^M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)$' >>> re.search(minta, 'MCMXL') ① <_sre.SRE_Match object at 0x008EEB48> >>> re.search(minta, 'MCML') ② <_sre.SRE_Match object at 0x008EEB48> >>> re.search(minta, 'MCMLX') ③ <_sre.SRE_Match object at 0x008EEB48> >>> re.search(minta, 'MCMLXXX') ④ <_sre.SRE_Match object at 0x008EEB48> >>> re.search(minta, 'MCMLXXXX') ⑤ >>>
M
karakterre, majd a CM
-re, majd az XL
-re, végül a karakterlánc végére. Ne feledd, a (A|B|C)
szintaxis azt jelenti: „illeszkedjen az A, B vagy C közül pontosan egyre”. Az XL
illeszkedik, így az XC
és L?X?X?X?
lehetőségek figyelmen kívül maradnak, és az illesztés a karakterlánc végével folytatódik. Az MCMXL
az 1940
római számokkal leírt változata.
M
karakterre, majd a CM
-re, majd az L?X?X?X?
-re. Az L?X?X?X?
-ből az L
-re illeszkedik, és kihagyja mind a három elhagyható X
karaktert. Ezután a karakterlánc végére lép. Az MCML
az 1950
római számokkal leírt változata.
M
karakterre, majd a CM
-re, majd az elhagyható L
-re és az első elhagyható X
-re, kihagyja a második és harmadik elhagyható X
-et, végül a karakterlánc végére is illeszkedik. Az MCMLX
az 1960
római számokkal leírt változata.
M
karakterre, majd a CM
-re, majd az elhagyható L
-re és mind a három elhagyható X
-re, végül a karakterlánc végére is illeszkedik. Az MCMLXXX
az 1980
római számokkal leírt változata.
M
karakterre, majd a CM
-re, majd az elhagyható L
-re és mind a három elhagyható X
-re, végül a karakterlánc végére nem illeszkedik, mert még hátra van egy nem illeszkedő X
. Így végül a teljes minta nem fog illeszkedni, és a None
objektumot adja vissza. Az MCMLXXXX
nem érvényes római szám.
Az egyes helyiértékek kifejezése ugyanazt a mintát követi. Megkíméllek a részletektől, és csak a végeredményt mutatom.
>>> minta = '^M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)(IX|IV|V?I?I?I?)$'
Hogy fog ez kinézni az alternatív {n,m}
szintaxis használatával? Ez a példa bemutatja az új szintaxist.
>>> minta = '^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$' >>> re.search(minta, 'MDLV') ① <_sre.SRE_Match object at 0x008EEB48> >>> re.search(minta, 'MMDCLXVI') ② <_sre.SRE_Match object at 0x008EEB48> >>> re.search(minta, 'MMMDCCCLXXXVIII') ③ <_sre.SRE_Match object at 0x008EEB48> >>> re.search(minta, 'I') ④ <_sre.SRE_Match object at 0x008EEB48>
M
karakterből egyre, majd a D?C{0,3}
kifejezésre. Ebből az elhagyható D
karakterre illeszkedik, és a három lehetséges C
karakterből nullára. Továbbhaladva illeszkedik a L?X{0,3}
kifejezésre is, ebből az elhagyható L
karakterre, és a három lehetséges X
karakterből nullára. Ezután illeszkedik a V?I{0,3}
kifejezésre, ebből az elhagyható V
karakterre és a három lehetséges I
karakterből nullára, végül a karakterlánc végére. Az MDLV
az 1555
római számokkal leírt változata.
M
karakterből kettőre, majd a D?C{0,3}
kifejezésből a D
-re és a lehetséges három C
karakterből egyre, majd az L?X{0,3}
kifejezésből az L
-re és a lehetséges három X
karakterből egyre, majd a V?I{0,3}
kifejezésből a V
-re és a három lehetséges I
karakterből egyre, végül a karakterlánc végére is illeszkedik. Az MMDCLXVI
a 2666
római számokkal leírt változata.
M
karakterből háromra, majd a D?C{0,3}
kifejezésből a D
-re és a három C
karakterből háromra, majd az L?X{0,3}
kifejezésből az L
-re és a három X
karakterből háromra, majd a V?I{0,3}
kifejezésből a V
-re és a három lehetséges I
karakterből háromra, végül a karakterlánc végére is illeszkedik. Az MMMDCCCLXXXVIII
a 3888
római számokkal leírt változata, és a kiterjesztett szintaxis nélkül leírható leghosszabb római szám.
M
-ből nullára illeszkedik, majd a D?C{0,3}
kifejezésből kihagyja az elhagyható D
-t és a három C
-ből nullára illeszkedik, majd az L?X{0,3}
kifejezésből kihagyja az elhagyható L
-et és a három X
-ből nullára illeszkedik, majd a V?I{0,3}
kifejezésből kihagyja az elhagyható V
-t és a három I
-ből egyre illeszkedik. Ezután a karakterlánc végére illeszkedik. Bámulatos, hol tart már a tudomány.
Ha ezt mind követted és elsőre megértetted, akkor jobb vagy mint én voltam. Most képzeld el, hogy valaki más reguláris kifejezéseit próbálod megérteni, egy nagy program kritikus függvényének a közepén. Vagy akár csak a saját reguláris kifejezéseid továbbfejlesztését képzeld el pár hónap múlva. Csináltam ilyet, és nem szép látvány.
Most pedig fedezzünk fel egy alternatív szintaxist, amely segíthet a kifejezések karbantarthatóan megírni.
⁂
Amikkel eddig foglalkoztunk, azokat „tömör” reguláris kifejezéseknek hívom. Amint láthattad, nehezen olvashatók és még ha rá is jössz, hogy mit csinálnak, nincs rá garancia, hogy hat hónap múlva is meg fogod érteni őket. Amire igazán szükség van, az a beágyazott dokumentáció.
A Python ezt lehetővé teszi az úgynevezett részletes reguláris kifejezésekkel. A részletes reguláris kifejezés két tekintetben tér el a tömör reguláris kifejezéstől:
#
karakterrel kezdődik, és a sor végéig tart. Ebben az esetben ez egy többsoros karakterláncon belüli megjegyzés, de ugyanúgy használható.
Egy példán keresztül mindjárt világosabb lesz. Dolgozzuk át a korábban használt tömör reguláris kifejezést, és alakítsuk részletes reguláris kifejezéssé. Ez a példa bemutatja ennek módját.
>>> minta = ''' ^ # karakterlánc eleje M{0,3} # ezresek - 0 és 3 közti M (CM|CD|D?C{0,3}) # százasok - 900 (CM), 400 (CD), 0-300 (0 és 3 közti C), # vagy 500-800 (D, amelyet 0 és 3 közti C követ) (XC|XL|L?X{0,3}) # tizesek - 90 (XC), 40 (XL), 0-30 (0 és 3 közti X), # vagy 50-80 (L, amelyet 0 és 3 közti X követ) (IX|IV|V?I{0,3}) # egyesek - 9 (IX), 4 (IV), 0-3 (0 és 3 közti I), # vagy 5-8 (V, amelyet 0 és 3 közti I követ) $ # karakterlánc vége ''' >>> re.search(minta, 'M', re.VERBOSE) ① <_sre.SRE_Match object at 0x008EEB48> >>> re.search(minta, 'MCMLXXXIX', re.VERBOSE) ② <_sre.SRE_Match object at 0x008EEB48> >>> re.search(minta, 'MMMDCCCLXXXVIII', re.VERBOSE) ③ <_sre.SRE_Match object at 0x008EEB48> >>> re.search(minta, 'M') ④
re.VERBOSE
konstans. Ezt a re
modul definiálja, és ez jelzi, hogy a mintát részletes reguláris kifejezésként kell kezelni. Amint láthatod, a minta meglehetősen sok üres helyet tartalmaz (mind figyelmen kívül marad), és jónéhány megjegyzés is van benne (mind figyelmen kívül marad). Az üres helyek és a megjegyzések figyelmen kívül hagyása után ez pontosan ugyanaz a reguláris kifejezés, mint amit az előző szakaszban láttál, de sokkal olvashatóbb.
M
karakter egyikére, majd a CM
-re, majd az L
-re és a három lehetséges X
-re, majd az IX
-re, végül a karakterlánc végére is illeszkedik.
M
mindegyikére, majd a D
-re és a három lehetséges C
mindegyikére, majd az L
-re és a három lehetséges X
mindegyikére, majd a V
-re és a három lehetséges I
mindegyikére, végül a karakterlánc végére is illeszkedik.
re.VERBOSE
jelzővel, így a re.search
függvény a mintát tömör reguláris kifejezésként kezeli, amelyben sok üres hely és kettőskereszt jelek vannak. A Python nem tudja automatikusan felismerni, hogy egy reguláris kifejezés részletes-e vagy sem. A Python feltételezi, hogy minden reguláris kifejezés tömör, hacsak nem mondod azt kifejezetten, hogy részletes.
⁂
Eddig teljes minták illesztésére koncentráltál. A minta vagy illeszkedik, vagy nem. De a reguláris kifejezések ennél sokkal hatékonyabbak. Amikor egy reguláris kifejezés illeszkedik, akkor az egyes részeit kiemelheted. Meghatározhatod, hogy mi hol illeszkedett.
Ez a példa egy másik valós életbeli problémából jön, amellyel egy korábbi munkahelyemen találkoztam. A probléma: egy amerikai telefonszám értelmezése. Az ügyfél a szám szabad formátumú megadására kért lehetőséget (egyetlen mezőben), de ezután a körzetszámot, törzset, számot és egy elhagyható melléket külön-külön szerette volna tárolni a cég adatbázisában. Átkutattam a webet, és sok példát találtam olyan reguláris kifejezésekre, amelyek erre szolgáltak, de egyik sem volt elég megengedő.
Az alábbi telefonszámok elfogadására kellett képesnek lennie a kódnak:
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
Micsoda változatosság! Ezen esetek mindegyikében tudnom kellett, hogy a körzetszám a 800
, a törzs az 555
a telefonszám többi része pedig az 1212
volt. A mellékkel rendelkezők esetén tudnom kellett, hogy a mellék az 1234
volt.
Haladjunk végig a telefonszám-értelmezés megoldásának kifejlesztésén. Ez a példa bemutatja az első lépést.
>>> telefonMinta = re.compile(r'^(\d{3})-(\d{3})-(\d{4})$') ① >>> telefonMinta.search('800-555-1212').groups() ② ('800', '555', '1212') >>> telefonMinta.search('800-555-1212-1234') ③ >>> telefonMinta.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})
-ra. Mi az a \d{3}
? Nos, a \d
azt jelenti: „tetszőleges numerikus számjegy” (0-tól 9
-ig). A {3}
azt jelenti: „illessz pontosan három numerikus számjegyet”; ez a korábban látott {n,m} szintaxis
egy változata. Az egész zárójelek közé tétele azt jelenti: „illessz pontosan három numerikus számjegyet, és jegyezd meg azokat később lekérdezhető csoportként”. Ezután egy literális kötőjelre illeszkedik. Ezután egy újabb, három számjegyből álló csoportra illeszkedik. Ezután egy újabb literális kötőjelre illeszkedik. Ezután egy újabb, pontosan négy számjegyből álló csoportra. Ezután a karakterlánc végére illeszkedik.
groups()
metódust a search()
metódus által visszaadott objektumon. Ez a reguláris kifejezésben megadott számú csoportot tartalmazó tuple-t ad vissza. Ebben az esetben három csoportot adtál meg, egy háromjegyűt, egy másik háromjegyűt és egy négyjegyűt.
search()
és groups()
metódusokat az éles kódban. Ha a search()
metódus nem ad vissza találatot, akkor a None
objektumot adja vissza, nem pedig egy reguláris kifejezés találati objektumot. A None.groups()
hívása egy teljesen nyilvánvaló kivételt dob: a None
nem rendelkezik groups()
metódussal. (Természetesen ennél egy picivel kevésbé nyilvánvaló, amikor a kódod mélyéről kapod ezt a kivételt. Igen, ezt tapasztalatból mondom.)
>>> telefonMinta = re.compile(r'^(\d{3})-(\d{3})-(\d{4})-(\d+)$') ① >>> telefonMinta.search('800-555-1212-1234').groups() ② ('800', '555', '1212', '1234') >>> telefonMinta.search('800 555 1212 1234') ③ >>> >>> telefonMinta.search('800-555-1212') ④ >>>
groups()
metódus most egy négy elemű tuple-t ad vissza, mivel a reguláris kifejezés most négy megjegyzendő csoportot definiál.
A következő példa bemutatja azt a reguláris kifejezést, amely képes a telefonszám különböző részei közti elválasztók kezelésére.
>>> telefonMinta = re.compile(r'^(\d{3})\D+(\d{3})\D+(\d{4})\D+(\d+)$') ① >>> telefonMinta.search('800 555 1212 1234').groups() ② ('800', '555', '1212', '1234') >>> telefonMinta.search('800-555-1212-1234').groups() ③ ('800', '555', '1212', '1234') >>> telefonMinta.search('80055512121234') ④ >>> >>> telefonMinta.search('800-555-1212') ⑤ >>>
\D+
-re. Ez meg mi? Nos a \D
bármely karakterre illeszkedik, kivéve a numerikus számjegyeket, a +
jelentése pedig „legalább 1”. Így a \D+
legalább egy nem számjegy karakterre illeszkedik. Ezt kell használnod a kötőjel helyett a különböző elválasztók illesztésére.
\D+
használata a -
helyett azt jelenti, hogy mostantól azok a telefonszámok is illeszkednek, amelyek részeit kötőjelek helyett szóközök választják el.
A következő példa bemutatja azt a reguláris kifejezést, amely képes az elválasztók nélküli telefonszámok kezelésére.
>>> telefonMinta = re.compile(r'^(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$') ① >>> telefonMinta.search('80055512121234').groups() ② ('800', '555', '1212', '1234') >>> telefonMinta.search('800.555.1212 x1234').groups() ③ ('800', '555', '1212', '1234') >>> telefonMinta.search('800-555-1212').groups() ④ ('800', '555', '1212', '') >>> telefonMinta.search('(800)5551212 x1234') ⑤ >>>
+
lecserélése *
karakterekre. A telefonszám részei közti \D+
helyett mostantól a \D*
áll. Emlékszel, hogy a +
jelentése „legalább 1”? Nos, a *
jelentése „nulla vagy több”. Mostantól a reguláris kifejezés képes olyan telefonszámok értelmezésére is, amelyek egyáltalán nem tartalmaznak elválasztó karaktereket.
800
), majd nulla nem számjegy karaktert, majd egy három számjegyből álló megjegyzett csoportot (555
), majd nulla nem számjegy karaktert, majd egy négy számjegyből álló megjegyzett csoportot (1212
), majd nulla nem számjegy karaktert, majd egy tetszőleges számú számjegyből álló megjegyzett csoportot (1234
), majd a karakterlánc végét.
x
a mellék előtt.
groups()
metódus továbbra is egy négy elemű tuple-t ad vissza, de a negyedik elem egy üres karakterlánc.
A következő példa bemutatja a telefonszámok bevezető karaktereinek kezelésének módját.
>>> telefonMinta = re.compile(r'^\D*(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$') ① >>> telefonMinta.search('(800)5551212 ext. 1234').groups() ② ('800', '555', '1212', '1234') >>> telefonMinta.search('800-555-1212').groups() ③ ('800', '555', '1212', '') >>> telefonMinta.search('work 1-(800) 555.1212 #1234') ④ >>>
\D*
kifejezést, a nulla vagy több nem numerikus karaktert illeszted az első megjegyzett csoport (a körzetszám) előtt. Vedd észre, hogy ezeket a nem numerikus karaktereket nem jegyezteted meg (nincsenek zárójelek közt). Ha a kifejezés talál ilyeneket, akkor kihagyja őket, és a körzetszámot csak akkor kezdi el megjegyezni, amikor megtalálja.
\D*
már kezeli.)
800
), majd egy nem számjegy karakterre (a kötőjelre), majd egy három számjegyből álló megjegyzett csoportra (555
), majd egy nem számjegy karakterre (a kötőjelre), majd egy négy számjegyből álló megjegyzett csoportra (1212
), majd nulla nem számjegy karakterre, majd egy nulla számjegyből álló megjegyzett csoportra, majd a karakterlánc végére.
1
-es, de feltételeztük, hogy a körzetszám előtti összes kezdő karakter nem számjegy (\D*
). Aargh.
Tegyünk egy lépést hátra. Eddig a reguláris kifejezések mind a karakterlánc elejétől illeszkedtek. De most már látod, hogy a karakterlánc elején meghatározhatatlan mennyiségű, figyelmen kívül hagyandó szöveg állhat. Ahelyett, hogy ezt mindet megpróbálnád illeszteni, hogy aztán átléphesd, próbálkozzunk egy másik megközelítéssel: egyáltalán ne illesszük a karakterlánc elejét. A következő példa ezt a megközelítést mutatja be.
>>> telefonMinta = re.compile(r'(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$') ① >>> telefonMinta.search('work 1-(800) 555.1212 #1234').groups() ② ('800', '555', '1212', '1234') >>> telefonMinta.search('800-555-1212').groups() ③ ('800', '555', '1212', '') >>> telefonMinta.search('80055512121234').groups() ④ ('800', '555', '1212', '1234')
^
hiányát ebben a reguláris kifejezésben. Már nem illeszted a karakterlánc elejét. Semmi sem mondja, hogy a reguláris kifejezésednek a teljes bemenetre illeszkednie kell. A reguláris kifejezéseket kezelő alrendszer elvégzi a munka nehezét, meghatározza, hogy a beviteli karakterlánc hol kezd el illeszkedni, és onnan halad tovább.
Látod, hogy a reguláris kifejezések milyen gyorsan tudnak kikerülni az ellenőrzés alól? Vess egy gyors pillantást az előző iterációk bármelyikére. Meg tudod mondani a különbséget az egyik és a következő között?
Amíg még érted a végső választ (és ez a végső válasz, ha találtál egy olyan esetet, amit nem kezel, nem akarok róla tudni), írjuk ki részletes reguláris kifejezésként, mielőtt elfelejted a meghozott döntések okait.
>>> telefonMinta = re.compile(r''' # ne illessze a karakterlánc elejére, a szám bárhol elkezdődhet (\d{3}) # a körzetszám 3 számjegy (például: '800') \D* # elhagyható elválasztó, ez tetszőleges számú nem számjegy lehet (\d{3}) # a törzs 3 számjegy (például: '555') \D* # elhagyható elválasztó (\d{4}) # a szám többi része 4 számjegy (például: '1212') \D* # elhagyható elválasztó (\d*) # a mellék elhagyható és tetszőleges számú számjegy lehet $ # a karakterlánc vége ''', re.VERBOSE) >>> telefonMinta.search('work 1-(800) 555.1212 #1234').groups() ① ('800', '555', '1212', '1234') >>> telefonMinta.search('800-555-1212') ② ('800', '555', '1212', '')
⁂
Ez csak a reguláris kifejezések képességeit illusztráló jéghegy legapróbb csúcsa. Más szavakkal, noha mostanra már teljesen elborítottak a reguláris kifejezések, higgy nekem, még semmit sem láttál.
Mostanra ismerned kell a következő kifejezéseket:
^
a karakterlánc elejére illeszkedik.
$
a karakterlánc végére illeszkedik.
\b
a szóhatárra illeszkedik.
\d
bármely numerikus számjegyre illeszkedik.
\D
bármely nem numerikus karakterre illeszkedik.
x?
egy elhagyható x
karakterre illeszkedik (másszóval egy x
-re nulla vagy egy alkalommal illeszkedik).
x*
az x
-re nulla vagy több alkalommal illeszkedik.
x+
az x
-re egy vagy több alkalommal illeszkedik.
x{n,m}
egy x
karakterre legalább n
, de nem több, mint m
alkalommal illeszkedik.
(a|b|c)
az a
, b
vagy c
pontosan egyikére illeszkedik.
(x)
általánosságban egy megjegyzett csoport. Az illeszkedő értéket lekérheted a re.search
által visszaadott objektum groups()
metódusának segítségével.
A reguláris kifejezések nagyon hatékonyak, de nem jelentenek helyes megoldást minden problémára. Eleget kell tanulnod róluk, hogy tudd, mikor megfelelők, mikor oldják meg a problémáid és mikor okoznak több problémát, mint ahányat megoldanak.
© 2001–11 Mark Pilgrim