A reguláris kifejezések egy hatékony nyelv a szövegminták megfeleltetésére. Ez az oldal a Python-gyakorlatainkhoz elegendő alapszintű bevezetést ad magukról a reguláris kifejezésekről, és bemutatja, hogyan működnek a reguláris kifejezések Pythonban. A Python “re” modulja biztosítja a reguláris kifejezések támogatását.
A Pythonban egy reguláris kifejezéses keresés tipikusan így írható:
match = re.search(pat, str)
A re.search() metódus egy reguláris kifejezésmintát és egy karakterláncot vesz, és a karakterláncban keresi a mintát. Ha a keresés sikeres, a search() egy match objektumot ad vissza, ellenkező esetben None. Ezért a keresést általában rögtön egy if-kijelentés követi, amely teszteli, hogy a keresés sikeres volt-e, ahogy a következő példában látható, amely a ‘word:’ mintát keresi, amelyet egy 3 betűs szó követ (részletek alább):
str = 'an example word:cat!!'match = re.search(r'word:\w\w\w', str)# If-statement after search() tests if it succeededif match: print 'found', match.group() ## 'found word:cat'else: print 'did not find'
A kód match = re.search(pat, str)
a keresés eredményét egy “match” nevű változóban tárolja. Ezután az if utasítás teszteli a találatot — ha igaz, a keresés sikeres volt, és a match.group() a megfelelő szöveg (pl. ‘word:cat’). Ellenkező esetben, ha a match hamis (pontosabban None), akkor a keresés nem volt sikeres, és nincs megfelelő szöveg.
A minta karakterlánc elején lévő ‘r’ egy python “nyers” karakterláncot jelöl, amely változtatás nélkül áthalad a backslash-eken, ami nagyon hasznos a reguláris kifejezéseknél (a Java-nak nagy szüksége van erre a funkcióra!). Javaslom, hogy szokásból mindig ‘r’-rel írd a mintaszövegeket.
Basic Patterns
A reguláris kifejezések ereje abban rejlik, hogy nem csak rögzített karaktereket, hanem mintákat is megadhatnak. Itt vannak a legalapvetőbb minták, amelyek egyetlen karakterre illeszkednek:
- a, X, 9, < — a közönséges karakterek pontosan illeszkednek önmagukhoz. A metakarakterek, amelyek nem illeszkednek önmagukhoz, mert speciális jelentésük van, a következők: . ^ $ * + ? { \ | ( ) (részletek alább)
- . (egy pont) — minden egyes karakterrel egyezik, kivéve az újsor ‘\n’
- \w — (kisbetűs w) “szó” karakterrel egyezik: betűvel, számmal vagy alulvonallal . Megjegyzendő, hogy bár a “word” a mnemonikus kifejezés, ez csak egyetlen szó karakterrel egyezik meg, nem egy egész szóval. \W (nagybetűs W) bármely nem szó karakterrel megegyezik.
- \b — szó és nem szó közötti határvonal
- \s — (kisbetűs s) egyetlen szóköz karakterrel – szóköz, újsor, return, tabulátor, form . \S (nagybetűs S) bármely nem szóköz karakterrel megegyezik.
- \t, \n, \r — tabulátor, újsor, return
- \d — tizedesjegy (néhány régebbi regex segédprogram nem támogatja az \d-t, de az \w-t és az \s-t mind támogatja)
- ^ = start, $ = end — a karakterlánc elejére vagy végére illeszkedik
- \ — egy karakter “különlegességét” gátolja. Így például használjuk a \. jelet egy ponthoz vagy a \\\ jelet egy perjelhez. Ha nem vagyunk biztosak abban, hogy egy karakter különleges jelentéssel bír-e, mint például a ‘@’, akkor a karakter elé tehetünk egy perjelet, \@, hogy biztosítsuk, hogy csak karakterként kezeljük.
Basic Examples
Vicc: Hogy hívják a háromszemű disznót? piiig!
A szabályos kifejezésekkel történő keresés alapvető szabályai egy sztringen belüli minta keresésére a következők:
- A keresés a sztringen halad végig az elejétől a végéig, és az első talált egyezésnél áll meg
- A minta egészének kell megfelelnie, de nem az egész sztringnek
- Ha
match = re.search(pat, str)
sikeres, a megfelelés nem None és különösen a match.group() az illeszkedő szöveg
## Search for pattern 'iii' in string 'piiig'. ## All of the pattern must match, but it may appear anywhere. ## On success, match.group() is matched text. match = re.search(r'iii', 'piiig') # found, match.group() == "iii" match = re.search(r'igs', 'piiig') # not found, match == None ## . = any char but \n match = re.search(r'..g', 'piiig') # found, match.group() == "iig" ## \d = digit char, \w = word char match = re.search(r'\d\d\d', 'p123g') # found, match.group() == "123" match = re.search(r'\w\w\w', '@@abcd!!') # found, match.group() == "abc"
ismétlés
A dolgok érdekesebbé válnak, ha a + és * jelzőket használjuk a mintában lévő ismétlődés megadására
- + — a minta 1 vagy több előfordulása a bal oldalán, pl. ‘i+’ = egy vagy több i
- * — a minta 0 vagy több előfordulása a bal oldalán
- ? — a minta 0 vagy 1 előfordulásával egyezik meg balra
Leftmost & Largest
A keresés először megkeresi a minta bal szélső egyezését, majd megpróbálja a lehető legtöbbet felhasználni a karakterláncból — pl. + és * a lehető legmesszebbre megy (a + és *-ról azt mondják, hogy “mohó”).
Megismétlési példák
## i+ = one or more i's, as many as possible. match = re.search(r'pi+', 'piiig') # found, match.group() == "piii" ## Finds the first/leftmost solution, and within it drives the + ## as far as possible (aka 'leftmost and largest'). ## In this example, note that it does not get to the second set of i's. match = re.search(r'i+', 'piigiiii') # found, match.group() == "ii" ## \s* = zero or more whitespace chars ## Here look for 3 digits, possibly separated by whitespace. match = re.search(r'\d\s*\d\s*\d', 'xx1 2 3xx') # found, match.group() == "1 2 3" match = re.search(r'\d\s*\d\s*\d', 'xx12 3xx') # found, match.group() == "12 3" match = re.search(r'\d\s*\d\s*\d', 'xx123xx') # found, match.group() == "123" ## ^ = matches the start of string, so this fails: match = re.search(r'^b\w+', 'foobar') # not found, match == None ## but without the ^ it succeeds: match = re.search(r'b\w+', 'foobar') # found, match.group() == "bar"
E-mail példák
Tegyük fel, hogy az ‘xyz [email protected] lila majom’ karakterláncban lévő e-mail címet szeretnénk megtalálni. Ezt egy futó példaként fogjuk használni, hogy bemutassuk a reguláris kifejezések további funkcióit. Íme egy próbálkozás a r’\w+@\w+’ mintával:
str = 'purple [email protected] monkey dishwasher' match = re.search(r'\w+@\w+', str) if match: print match.group() ## 'b@google'
A keresés ebben az esetben nem kapja meg a teljes e-mail címet, mert a \w nem egyezik a címben lévő ‘-‘ vagy ‘.’ betűvel. Ezt az alábbiakban a reguláris kifejezések funkcióival fogjuk kijavítani.
Szögletes zárójelek
A szögletes zárójelek egy karakterkészlet jelölésére használhatók, így megfelel az ‘a’ vagy a ‘b’ vagy a ‘c’ jelnek. A \w, \s stb. kódok is működnek szögletes zárójelben, azzal az egy kivétellel, hogy a pont (.) csak egy szó szerinti pontot jelent. Az e-mailekkel kapcsolatos probléma esetén a szögletes zárójelek segítségével könnyen hozzáadhatjuk a ‘.’ és a ‘-‘ karaktereket a @ körül megjelenő karakterekhez a r’+@+’ mintával, hogy megkapjuk a teljes e-mail címet:
match = re.search(r'+@+', str) if match: print match.group() ## '[email protected]'
(További szögletes zárójelek) Használhatunk kötőjelet is a tartomány jelölésére, így az összes kisbetűre illik. Ha a kötőjelet tartomány megjelölése nélkül szeretné használni, tegye a kötőjelet utolsónak, pl. . A szögletes zárójel-készlet elején lévő felfelé mutató (^) megfordítja azt, így az ‘a’ vagy ‘b’ kivételével minden karaktert jelent.
Group Extraction
A reguláris kifejezések “group” funkciója lehetővé teszi, hogy az illeszkedő szöveg egyes részeit kiemelje. Tegyük fel, hogy az e-mailek problémájához külön-külön szeretnénk kinyerni a felhasználónevet és a hosztot. Ehhez tegyünk zárójelet ( ) a felhasználónév és a hoszt köré a mintában, például így: r'(+)@(+)’. Ebben az esetben a zárójelek nem változtatják meg, hogy a minta mire fog illeszkedni, hanem logikai “csoportokat” hoznak létre az illeszkedő szövegen belül. Sikeres keresés esetén a match.group(1) az 1. bal oldali zárójelnek megfelelő szöveg, a match.group(2) pedig a 2. bal oldali zárójelnek megfelelő szöveg. A sima match.group() továbbra is a teljes találati szöveg, mint általában.
str = 'purple [email protected] monkey dishwasher' match = re.search(r'(+)@(+)', str) if match: print match.group() ## '[email protected]' (the whole match) print match.group(1) ## 'alice-b' (the username, group 1) print match.group(2) ## 'google.com' (the host, group 2)
A szabályos kifejezésekkel kapcsolatos általános munkafolyamat az, hogy megírjuk a keresett dolog mintáját, és zárójeles csoportokat adunk hozzá a kívánt részek kivonásához.
findall
Afindall() valószínűleg a re modul legerősebb függvénye. Fentebb a re.search() függvényt használtuk egy minta első találatának megtalálására. findall() megtalálja *az összes* találatot, és stringek listájaként adja vissza őket, ahol minden string egy-egy találatot képvisel.
## Suppose we have a text with many email addresses str = 'purple [email protected], blah monkey [email protected] blah dishwasher' ## Here re.findall() returns a list of all the found email strings emails = re.findall(r'+@+', str) ## for email in emails: # do something with each found email string print email
findall With Files
Fájlok esetében szokás lehet, hogy egy hurkot írunk a fájl sorainak végigjárására, és ezután minden egyes soron meghívhatjuk a findall() függvényt. Ehelyett hagyd, hogy a findall() végezze el helyetted az iterációt — sokkal jobb! Csak tápláljuk be a teljes fájlszöveget a findall()-ba, és hagyjuk, hogy egyetlen lépésben visszaadja az összes találat listáját (emlékezzünk arra, hogy az f.read() a fájl teljes szövegét egyetlen karakterláncban adja vissza):
# Open file f = open('test.txt', 'r') # Feed the file text into findall(); it returns a list of all the found strings strings = re.findall(r'some pattern', f.read())
findall és csoportok
A zárójeles ( ) csoportmechanizmus kombinálható a findall()-val. Ha a minta 2 vagy több zárójeles csoportot tartalmaz, akkor a findall() a stringek listája helyett *tuplik* listáját adja vissza. Minden tuple a minta egy-egy találatát jelenti, és a tuple-n belül a group(1), group(2) … adatok vannak. Tehát ha az e-mail mintához 2 zárójeles csoportot adunk, akkor a findall() egy olyan tuple-ok listáját adja vissza, amelyek mindegyike 2 hosszúságú, és tartalmazza a felhasználónevet és a hosztot, pl. (‘alice’, ‘google.com’).
str = 'purple [email protected], blah monkey [email protected] blah dishwasher' tuples = re.findall(r'(+)@(+)', str) print tuples ## for tuple in tuples: print tuple ## username print tuple ## host
Amikor már megvan a tuple-ok listája, végighaladhatunk rajta, hogy minden egyes tuple-ra elvégezzünk néhány számítást. Ha a minta nem tartalmaz zárójelet, akkor a findall() a korábbi példákhoz hasonlóan a talált karakterláncok listáját adja vissza. Ha a minta egyetlen zárójeles csoportot tartalmaz, akkor a findall() az adott csoportnak megfelelő karakterláncok listáját adja vissza. (Homályos opcionális funkció: Néha vannak paren ( ) csoportosítások a mintában, amelyeket azonban nem akarunk kinyerni. Ebben az esetben írja a zárójeleket egy ?: -vel az elején, pl. (?: ), és ez a bal oldali zárójel nem számít csoportos eredménynek.)
RE munkafolyamat és hibakeresés
A szabályos kifejezésminták sok jelentést pakolnak néhány karakterbe , de annyira sűrűek, hogy sok időt tölthet a minták hibakeresésével. Állítsa be a futtatási időt úgy, hogy könnyen lefuttathasson egy mintát és kiírhassa, hogy mire illik, például úgy, hogy lefuttatja egy kis tesztszövegen és kiírja a findall() eredményét. Ha a minta nem talál semmit, próbáljuk meg gyengíteni a mintát, eltávolítva belőle részeket, így túl sok találatot kapunk. Ha nem talál semmit, akkor nem tudsz továbblépni, mivel nincs semmi konkrétum, amit megnézhetnél. Ha már túl sok egyezés van, akkor fokozatosan szigoríthatsz rajta, hogy pontosan azt találd el, amit szeretnél.
Options
A re függvények opciókkal módosíthatják a mintaillesztés viselkedését. Az opciós zászlót a search() vagy findall() stb. extra argumentumként adjuk hozzá, pl. re.search(pat, str, re.IGNORECASE).
- IGNORECASE — figyelmen kívül hagyja a nagy- és kisbetűk közötti különbségeket az illesztésnél, így az ‘a’ mind az ‘a’-ra, mind az ‘A’-ra illik.
- DOTALL — engedélyezi, hogy a pont (.) megfeleljen az újsornak — normál esetben mindenre megfelel, kivéve az újsorra. Ez megzavarhat — azt hiszed, hogy a .* mindenre illik, de alapértelmezés szerint nem lépi túl a sor végét. Vegye figyelembe, hogy a \s (whitespace) tartalmazza az újsorokat is, így ha egy olyan whitespace sorozatra akar illeszkedni, amely tartalmazhat újsorokat, akkor használhatja a \s*
- MULTILINE — Egy több sorból álló karakterláncban engedélyezze, hogy a ^ és $ az egyes sorok elejére és végére illeszkedjen. Normális esetben a ^/$ csak az egész karakterlánc elejére és végére illeszkedik.
Greedy vs. Non-Greedy (opcionális)
Ez egy opcionális rész, amely egy fejlettebb, a gyakorlatokhoz nem szükséges reguláris kifejezés technikát mutat be.
Tegyük fel, hogy van egy szövegünk, amelyben címkék vannak: <b>foo</b> és <i>so on</i>
Tegyük fel, hogy minden egyes tagre a ‘(<.*>)’ mintát próbálja illeszteni — mit talál először?
Az eredmény kissé meglepő, de a .* mohósága miatt az egész ‘<b>foo</b> és <i>so on</i>’-t egy nagy egyezésként egyezteti. A probléma az, hogy a .* addig megy, ameddig csak tud, ahelyett, hogy megállna az első >-nál (azaz “mohó”).
A reguláris kifejezéseknek van egy olyan kiterjesztése, ahol egy ?-t adunk a végére, például .*? vagy .+?, ami nem mohóvá változtatja őket. Most már megállnak, amint tudnak. Így a ‘(<.*?>)’ minta első találatként csak a ‘<b>’-t kapja, második találatként pedig a ‘</b>’-t, és így tovább, sorban megkapva minden <..> párt. A stílus jellemzően az, hogy egy .*? karaktert használsz, majd közvetlenül jobbra tőle keresel valami konkrét jelölőt (ebben az esetben >), ami kikényszeríti a .*? futás végét.
A *? kiterjesztés a Perl-ből származik, és a Perl kiterjesztéseit tartalmazó reguláris kifejezéseket Perl Compatible Regular Expressions — pcre néven ismerjük. A Python tartalmazza a pcre támogatást. Sok parancssori segédprogram stb. rendelkezik egy olyan flaggel, amellyel elfogadja a pcre mintákat.
Egy régebbi, de széles körben használt technika a “minden ilyen karakter, kivéve, hogy X-nél megáll” gondolat kódolására a szögletes zárójeles stílust használja. A fentiekhez megírhatod a mintát, de a .* helyett az összes karakter helyett használhatod a *-ot, amely minden olyan karaktert átugrik, amely nem > (a vezető ^ “megfordítja” a szögletes zárójelek készletét, így minden olyan karakterre illik, amely nincs a zárójelben).
Substitution (opcionális)
A re.sub(pat, replacement, str) függvény megkeresi a minta összes példányát az adott karakterláncban, és kicseréli azokat. A helyettesítő karakterlánc tartalmazhatja a ‘\1’, ‘\2’ kifejezéseket, amelyek az eredeti megfelelő szövegből a group(1), group(2) stb. szövegre utalnak.
Itt egy példa, amely megkeresi az összes e-mail címet, és úgy változtatja meg őket, hogy a felhasználó (\1) megmaradjon, de a yo-yo-dyne.com legyen a hoszt.
str = 'purple [email protected], blah monkey [email protected] blah dishwasher' ## re.sub(pat, replacement, str) -- returns new string with all replacements, ## is group(1), group(2) in the replacement print re.sub(r'(+)@(+)', r'@yo-yo-dyne.com', str) ## purple [email protected], blah monkey [email protected] blah dishwasher
GYakorlat
A szabályos kifejezések gyakorlására lásd a Babanevek gyakorlatot.