Säännölliset lausekkeet ovat tehokas kieli tekstikuvioiden yhteensovittamiseen. Tämä sivu antaa perustiedot säännöllisistä lausekkeista itsessään riittävät Python-harjoituksiimme ja näyttää, miten säännölliset lausekkeet toimivat Pythonissa. Pythonin ”re”-moduuli tarjoaa säännöllisten lausekkeiden tuen.
Pythonissa säännöllisen lausekkeen haku kirjoitetaan tyypillisesti seuraavasti:
match = re.search(pat, str)
Metodi re.search() ottaa säännöllisen lausekkeen kuvion ja merkkijonon ja etsii kuvion merkkijonon sisältä. Jos haku onnistuu, search() palauttaa match-olion tai muuten None. Siksi hakua seuraa yleensä välittömästi if-lauseke, jolla testataan, onnistuiko haku, kuten seuraavassa esimerkissä, jossa haetaan kuviota ’sana:’, jota seuraa kolmikirjaiminen sana (yksityiskohdat alla):
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'
Koodi match = re.search(pat, str)
tallentaa hakutuloksen muuttujaan nimeltä ”match”. Sitten if-lauseke testaa matchin — jos true, haku onnistui ja match.group() on täsmäävä teksti (esim. ’sana:kissa’). Muussa tapauksessa, jos match on false (tarkemmin sanottuna None), haku ei onnistunut, eikä täsmäävää tekstiä ole.
Kuvio-merkkijonon alussa oleva ’r’ tarkoittaa pythonin ”raw”-merkkijonoa, joka läpäisee backslash-merkkijonot muuttumattomina, mikä on erittäin kätevää säännöllisille lausekkeille (Java tarvitsee tätä ominaisuutta kipeästi!). Suosittelen, että kirjoitat kuvio-merkkijonot aina ’r:llä’ ihan vain tavan vuoksi.
Peruskuviot
Regulaarilausekkeiden voima on siinä, että niillä voi määritellä kuvioita, ei vain kiinteitä merkkejä. Tässä ovat perustavanlaatuisimmat kuviot, jotka sopivat yksittäisiin merkkeihin:
- a, X, 9, < — tavalliset merkit sopivat vain täsmälleen itseensä. Metamerkit, jotka eivät sovi itseensä, koska niillä on erityisiä merkityksiä, ovat: . ^ $ * + ? { \ | ( ) (yksityiskohdat jäljempänä)
- . (piste) — vastaa mitä tahansa yksittäistä merkkiä paitsi rivinvaihtoa ’\n’
- \w — (pieni w) vastaa ”sana”-merkkiä: kirjainta, numeroa tai alaviivaa . Huomaa, että vaikka ”sana” on tämän muistisana, se vastaa vain yksittäistä sanamerkkiä, ei kokonaista sanaa. \W (iso W) vastaa mitä tahansa muuta kuin sanamerkkiä.
- \b — sanan ja ei-sanan välinen raja
- \s — (pieni s) vastaa yksittäistä välilyönti-merkkiä — välilyönti, uusi viiva, return, tabulaattori, muoto . \S (iso S) vastaa mitä tahansa muuta kuin välilyönti-merkkiä.
- \t, \n, \r — tabulaattori, uusi rivi, return
- \d — desimaaliluku (jotkin vanhemmat regex-apuohjelmat eivät tue muuta kuin \d:tä, mutta kaikki tukevat \w:tä ja \s:ää)
- ^ = alku, $ = loppu — täsmää merkkijonon alku- tai loppupäähän
- \ — estää merkin ”erikoisaseman”. Käytä siis esimerkiksi \. vastaamaan pistettä tai \\ vastaamaan vinoviivaa. Jos et ole varma, onko jollakin merkillä erityinen merkitys, kuten ’@’, voit laittaa sen eteen vinoviivan, \@, varmistaaksesi, että sitä käsitellään vain merkkinä.
Basic Examples
Vitsi: miksi kutsutaan sikaa, jolla on kolme silmää? piiig!
Säännöllisen lausekehaun perussäännöt merkkijonon sisällä olevan kuvion etsimiseksi ovat:
- Haku etenee merkkijonon läpi alusta loppuun ja pysähtyy ensimmäiseen löydettyyn täsmäämiseen
- Koko kuvion on täsmättävä, mutta ei koko merkkijonon
- Jos
match = re.search(pat, str)
onnistuu, täsmäämiseen ei ole None ja erityisesti täsmäämiseen match.group() on täsmäävä teksti
## 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"
Kertaus
Asiat muuttuvat mielenkiintoisemmiksi, kun käytät + ja * -merkkejä määrittäessäsi toistoa kuviossa
- + — 1 tai useampi esiintymä kuviosta sen vasemmalla puolella, esim. ’i+’ = yksi tai useampi i
- * — 0 tai useampi esiintymä kuviosta sen vasemmalla puolella
- ? — vastaa 0 tai 1 esiintymää kuvion vasemmalla puolella
Vasemmanpuoleisin & Suurin
Haku etsii ensin kuvion vasemmanpuoleisimman osuman ja pyrkii sitten käyttämään mahdollisimman suuren osan merkkijonosta — esim. + ja * menevät niin pitkälle kuin mahdollista (+:n ja *:n sanotaan olevan ”ahneita”).
Kertaus Esimerkkejä
## 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"
Sähköpostit Esimerkki
Esitellään, että halutaan löytää sähköpostiosoite merkkijonon ’xyz [email protected] purple monkey’ sisältä. Käytämme tätä juoksevana esimerkkinä demonstroidaksemme lisää säännöllisten lausekkeiden ominaisuuksia. Tässä yritetään käyttää mallia r’\w+@\w+’:
str = 'purple [email protected] monkey dishwasher' match = re.search(r'\w+@\w+', str) if match: print match.group() ## 'b@google'
Haku ei tässä tapauksessa saa koko sähköpostiosoitetta, koska \w ei sovi osoitteessa oleviin ’-’ tai ’.’. Korjaamme tämän käyttämällä alla olevia säännöllisen lausekkeen ominaisuuksia.
Neliösulkeet
Neliösulkeita voidaan käyttää osoittamaan joukko merkkejä, joten ne sopivat ’a’ tai ’b’ tai ’c’. Koodit \w, \s jne. toimivat myös hakasulkeiden sisällä sillä poikkeuksella, että piste (.) tarkoittaa vain kirjaimellista pistettä. Sähköpostiongelmassa hakasulkeet ovat helppo tapa lisätä ’.’ ja ’-’ merkkien joukkoon, jotka voivat esiintyä @-kirjaimen ympärillä kuvion r’+@+’ avulla, jolloin saadaan koko sähköpostiosoite:
match = re.search(r'+@+', str) if match: print match.group() ## '[email protected]'
(Lisää hakasulkujen ominaisuuksia) Voit myös käyttää ajatusviivaa osoittamaan aluetta, jolloin kaikki pienet kirjaimet sopivat. Jos haluat käyttää ajatusviivaa osoittamatta vaihteluväliä, laita ajatusviiva viimeiseksi, esim. . Hattu ylöspäin (^) hakasulkujoukon alussa kääntää sen päinvastaiseksi, joten se tarkoittaa mitä tahansa merkkiä paitsi ’a’ tai ’b’.
Ryhmien poiminta
Säännöllisen lausekkeen ”ryhmä”-ominaisuuden avulla voit poimia osia täsmäävästä tekstistä. Oletetaan, että sähköpostiongelmaa varten haluamme poimia erikseen käyttäjätunnuksen ja hostin. Voit tehdä tämän lisäämällä hakasulkeet ( ) kuvion käyttäjätunnuksen ja isäntäkoneen ympärille, esimerkiksi näin: r'(+)@(+)’. Tässä tapauksessa sulkeet eivät muuta sitä, mitä kuvio vastaa, vaan ne muodostavat loogisia ”ryhmiä” täsmäävän tekstin sisälle. Onnistuneessa haussa match.group(1) on 1. vasemmanpuoleista sulkeumaa vastaava teksti ja match.group(2) on 2. vasemmanpuoleista sulkeumaa vastaava teksti. Pelkkä match.group() on edelleen koko otteluteksti kuten tavallisesti.
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)
Yleinen työnkulku säännöllisten lausekkeiden kanssa on, että kirjoitat kuvion etsimällesi asialle ja lisäät sulkujen ryhmät poimiaksesi haluamasi osat.
findall
findall() on luultavasti re-moduulin tehokkain yksittäinen funktio. Edellä käytimme re.search()-funktiota löytääksemme kuvion ensimmäisen osuman. findall()-funktio etsii *kaikki* osumat ja palauttaa ne merkkijonojen listana, jossa jokainen merkkijono edustaa yhtä osumaa.
## 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
Tiedostojen kohdalla sinulla voi olla tapana kirjoittaa silmukka, jolla käydään läpi tiedoston rivit, ja sen jälkeen voisit kutsua findall()-funktiota jokaiselle riville. Anna sen sijaan findall():n tehdä iterointi puolestasi — paljon parempi! Syötät vain koko tiedoston tekstin findall()-funktioon ja annat sen palauttaa listan kaikista osumista yhdessä vaiheessa (muista, että f.read()-funktio palauttaa koko tiedoston tekstin yhtenä merkkijonona):
# 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 ja ryhmät
Sulkeisten ( ) ryhmämekanismi voidaan yhdistää findall()-funktioon. Jos kuvio sisältää 2 tai useampia sulkujen ryhmiä, findall() palauttaa merkkijonojen luettelon sijasta *tupelien* luettelon. Jokainen tuple edustaa yhtä kuvion vastaavuutta, ja tuplen sisällä on group(1), group(2) … data. Jos siis sähköpostikuvioon lisätään 2 suluissa olevaa ryhmää, findall() palauttaa listan tupleja, joista kukin on pituudeltaan 2 ja jotka sisältävät käyttäjätunnuksen ja isännän, esimerkiksi (’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
Kun sinulla on tupleluettelo, voit käydä sen läpi silmukassa ja tehdä laskutoimituksia jokaiselle tuplelle. Jos kuvio ei sisällä sulkuja, findall() palauttaa listan löydetyistä merkkijonoista, kuten aiemmissa esimerkeissä. Jos kuvio sisältää yhden sulkujen joukon, findall() palauttaa luettelon merkkijonoista, jotka vastaavat tätä yhtä ryhmää. (Hämärä valinnainen ominaisuus: Joskus kuviossa on paren ( ) -ryhmittymiä, joita ei kuitenkaan haluta poimia. Siinä tapauksessa kirjoita parenssit alkupäähän ?:, esim. (?: ), ja tuota vasenta parenia ei lasketa ryhmätulokseksi.)
RE työnkulku ja virheenkorjaus
Regulaariset lausekekuviot paketoivat paljon merkitystä muutamaan merkkiin , mutta ne ovat niin tiiviitä, että niiden virheenkorjaukseen voi kulua paljon aikaa. Aseta ajoaikasi niin, että voit ajaa kuvion ja tulostaa sen osumat helposti, esimerkiksi ajamalla sen pieneen testitekstiin ja tulostamalla findall():n tuloksen. Jos kuvio ei täsmää mihinkään, yritä heikentää kuviota poistamalla siitä osia, jotta saat liian monta täsmäämistä. Kun se ei täsmää mihinkään, et pääse eteenpäin, koska ei ole mitään konkreettista katsottavaa. Kun se täsmää liikaa, voit kiristää sitä vähitellen, jotta löydät juuri sen, mitä haluat.
Options
Re-funktiot ottavat vastaan vaihtoehtoja, joilla voit muuttaa kuvion täsmäämisen käyttäytymistä. Optiolippu lisätään ylimääräisenä argumenttina search()- tai findall()-funktioon jne., esim. re.search(pat, str, re.IGNORECASE).
- IGNORECASE — jätä huomioimatta isojen ja pienten kirjainten väliset erot täsmäytyksessä, joten ’a’ täsmää sekä ’a’:n että ’A:n’ kanssa.
- DOTALL — sallii pisteen (.) täsmäävän rivinvaihtoon — normaalisti se täsmää kaikkeen muuhun paitsi rivinvaihtoon. Tämä voi hämätä — luulet, että .* sopii kaikkeen, mutta oletusarvoisesti se ei mene rivin loppua pidemmälle. Huomaa, että \s (välilyönti) sisältää myös uudet viivat, joten jos haluat sovittaa välilyönnin, joka voi sisältää uuden viivan, voit vain käyttää \s*
- MULTILINE — Monesta rivistä koostuvassa merkkijonossa sallitaan ^:n ja $:n sovittaminen jokaisen rivin alkuun ja loppuun. Normaalisti ^/$ sopisi vain koko merkkijonon alkuun ja loppuun.
Greedy vs. Non-Greedy (valinnainen)
Tämä on valinnainen osio, jossa esitellään edistyneempi säännöllisten lausekkeiden tekniikka, jota ei tarvita harjoituksissa.
Esitettäkö, että sinulla on tekstiä, jossa on tunnisteita: <b>foo</b> ja <i>so on</i>
Esitä, että yrität sovittaa jokaista tagia malliin ’(<.*>)’ — mitä se sovittaa ensin?
Tulos on hieman yllättävä, mutta .*:n ahneus saa sen sovittamaan koko ’<b>foo</b> ja <i>so on</i>’ yhdeksi isoksi matchiksi. Ongelmana on, että .* menee niin pitkälle kuin se voi, eikä pysähdy ensimmäiseen > (eli se on ”ahne”).
Säännöllisiin lausekkeisiin on olemassa laajennus, jossa lisäät ?:n loppuun, kuten .*? tai .+?, jolloin ne muuttuvat ei-ahneiksi. Nyt ne pysähtyvät heti kun voivat. Joten kuvio ’(<.*?>)’ saa vain ’<b>’ ensimmäisenä täsmäämisenä ja ’</b>’ toisena täsmäämisenä, ja niin edelleen saaden jokaisen <..> parin vuorollaan. Tyyli on tyypillisesti se, että käytät .*?, ja sitten heti sen oikealla puolella etsit jotain konkreettista merkkiä (tässä tapauksessa >), joka pakottaa .*?-juoksun päättymään.
*?-laajennus on peräisin Perlistä, ja säännölliset lausekkeet, jotka sisältävät Perlin laajennuksia, tunnetaan nimellä Perl-yhteensopivat säännönmukaiset lausekkeet — pcre. Python sisältää pcre-tuen. Monissa komentorivin apuohjelmissa jne. on lippu, jolla ne hyväksyvät pcre-malleja.
Vanhempi mutta laajalti käytetty tekniikka koodata tämä ajatus ”kaikki nämä merkit paitsi pysähtyminen kohtaan X” käyttää neliösulkutyyliä. Yllä olevaan voit kirjoittaa kuvion, mutta .*:n sijasta kaikkien merkkien saamiseksi voit käyttää *:ää, joka ohittaa kaikki merkit, jotka eivät ole > (johtava ^ ”kääntää” hakasulkujen sarjan, joten se sopii mihin tahansa merkkiin, joka ei ole hakasulkujen sisällä).
Korvaaminen (valinnainen)
Funktio re.sub(pat, replacement, str) etsii kaikki kuvion esiintymät annetusta merkkijonosta ja korvaa ne. Korvaava merkkijono voi sisältää ’\1’, ’\2’, jotka viittaavat tekstiin ryhmästä(1), ryhmästä(2) ja niin edelleen alkuperäisestä vastaavasta tekstistä.
Tässä on esimerkki, jossa etsitään kaikki sähköpostiosoitteet ja muutetaan ne siten, että käyttäjä (\1) säilyy, mutta isäntänä on yo-yo-dyne.com.
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
Harjoitus
Regulaaristen lausekkeiden harjoittelua voi harjoitella vauvojen nimiä käsittelevässä harjoituksessa.