Expresiile regulate sunt un limbaj puternic pentru potrivirea tiparelor de text. Această pagină oferă o introducere de bază în expresiile regulate în sine, suficientă pentru exercițiile noastre Python și arată cum funcționează expresiile regulate în Python. Modulul Python „re” oferă suport pentru expresii regulate.
În Python, o căutare prin expresii regulate este scrisă de obicei astfel:
match = re.search(pat, str)
Metoda re.search() ia un model de expresie regulată și un șir de caractere și caută acel model în interiorul șirului. Dacă căutarea are succes, search() returnează un obiect match sau None în caz contrar. Prin urmare, căutarea este, de obicei, urmată imediat de o declarație if pentru a testa dacă căutarea a reușit, așa cum se arată în exemplul următor care caută modelul „word:” urmat de un cuvânt de 3 litere (detalii mai jos):
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'
Codul match = re.search(pat, str)
stochează rezultatul căutării într-o variabilă numită „match”. Apoi, enunțul if testează „match” — dacă este adevărat, căutarea a reușit, iar match.group() este textul care corespunde (de exemplu, „word:cat”). În caz contrar, dacă potrivirea este falsă (None pentru a fi mai specific), atunci căutarea nu a avut succes și nu există nici un text corespunzător.
„r” de la începutul șirului de tipare desemnează un șir „brut” python care trece prin backslash-uri fără modificări, ceea ce este foarte util pentru expresiile regulate (Java are mare nevoie de această caracteristică!). Vă recomand să scrieți întotdeauna șirurile de tipare cu „r”, doar ca un obicei.
Basic Patterns
Puterea expresiilor regulate este că pot specifica tipare, nu doar caractere fixe. Iată cele mai de bază tipare care se potrivesc cu un singur caracter:
- a, X, 9, < — caracterele obișnuite se potrivesc pur și simplu exact. Metacaracterele care nu se potrivesc singure pentru că au semnificații speciale sunt: . ^ $ * + ? { \ | ( ) (detalii mai jos)
- . (un punct) — se potrivește cu orice caracter unic, cu excepția liniei noi ‘\n’
- \w — (w minusculă) se potrivește cu un caracter „cuvânt”: o literă sau o cifră sau o bară de subsol . Rețineți că, deși „word” este mnemotehnica pentru aceasta, se potrivește doar cu un singur caracter de cuvânt, nu cu un cuvânt întreg. \W (W majusculă) se potrivește cu orice caracter care nu este un cuvânt.
- \b — limită între cuvânt și non-cuvânt
- \s — (s minusculă) se potrivește cu un singur caracter de spațiu alb — spațiu, newline, return, tab, form . \S (S majusculă) se potrivește cu orice caracter care nu este spațiu alb.
- \t, \n, \r — tab, newline, return
- \d — cifră zecimală (unele utilitare regex mai vechi nu suportă decât \d, dar toate suportă \w și \s)
- ^ = start, $ = end — se potrivește cu începutul sau sfârșitul șirului
- \ — inhibă „caracterul special” al unui caracter. Deci, de exemplu, folosiți \. pentru a potrivi un punct sau \\\ pentru a potrivi o bară oblică. Dacă nu sunteți sigur că un caracter are o semnificație specială, cum ar fi „@”, puteți pune o bară oblică în fața lui, \@, pentru a vă asigura că este tratat doar ca un caracter.
Exemple de bază
Jokes: cum se numește un porc cu trei ochi? piiig!
Regulele de bază ale căutării cu expresii regulate pentru un model într-un șir de caractere sunt:
- Căutarea se desfășoară prin șir de la început până la sfârșit, oprindu-se la prima potrivire găsită
- Trebuie să se potrivească tot modelul, dar nu tot șirul
- Dacă
match = re.search(pat, str)
are succes, potrivirea nu este None și în special match.group() este textul care se potrivește
## 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"
## 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"
Repetiție
Lucrurile devin mai interesante atunci când folosiți + și * pentru a specifica repetiția în tipar
- + — 1 sau mai multe apariții ale tiparului în stânga sa, de exemplu ‘i+’ = unul sau mai mulți i
- * — 0 sau mai multe apariții ale tiparului în stânga sa
- ? — se potrivește cu 0 sau 1 apariții ale modelului din stânga sa
Cel mai din stânga & Cel mai mare
În primul rând, căutarea găsește cea mai din stânga potrivire a modelului și, în al doilea rând, încearcă să folosească cât mai mult din șirul de caractere posibil – de ex. + și * merg cât mai departe posibil (se spune că + și * sunt „lacomi”).
Exemple de repetare
## 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"
Exemplu de e-mailuri
Să presupunem că doriți să găsiți adresa de e-mail în interiorul șirului ‘xyz [email protected] purple monkey’. Vom folosi acest lucru ca exemplu de funcționare pentru a demonstra mai multe caracteristici ale expresiilor regulate. Iată o încercare folosind modelul r’\w+@\w+’:
str = 'purple [email protected] monkey dishwasher' match = re.search(r'\w+@\w+', str) if match: print match.group() ## 'b@google'
str = 'purple [email protected] monkey dishwasher' match = re.search(r'\w+@\w+', str) if match: print match.group() ## 'b@google'
Cercetarea nu obține întreaga adresă de e-mail în acest caz, deoarece \w nu se potrivește cu ‘-‘ sau ‘.’ din adresă. Vom remedia acest lucru folosind funcțiile expresiilor regulate de mai jos.
Square Brackets
Square brackets poate fi folosit pentru a indica un set de caractere, deci se potrivește cu ‘a’ sau ‘b’ sau ‘c’. Codurile \w, \s etc. funcționează și ele în interiorul parantezelor pătrate, cu singura excepție că punctul (.) înseamnă doar un punct literal. Pentru problema e-mailurilor, parantezele pătrate sunt o modalitate ușoară de a adăuga „.” și „-” la setul de caractere care pot apărea în jurul lui @ cu modelul r’+@+’ pentru a obține întreaga adresă de e-mail:
match = re.search(r'+@+', str) if match: print match.group() ## '[email protected]'
(Mai multe caracteristici ale parantezelor pătrate) De asemenea, puteți utiliza o liniuță pentru a indica un interval, astfel încât să se potrivească tuturor literelor minuscule. Pentru a utiliza o liniuță fără a indica un interval, puneți liniuța la sfârșit, de exemplu . O pălărie în sus (^) la începutul unui set de paranteze pătrate îl inversează, deci înseamnă orice caracter cu excepția lui „a” sau „b”.
Extragerea grupurilor
Caracteristica „grup” a unei expresii regulate vă permite să selectați părți din textul care se potrivește. Să presupunem că, pentru problema emailurilor, dorim să extragem separat numele de utilizator și gazda. Pentru a face acest lucru, adăugați paranteze ( ) în jurul numelui de utilizator și al gazdei din model, astfel: r”(+)@(+)”. În acest caz, parantezele nu schimbă ceea ce se va potrivi cu modelul, ci stabilesc „grupuri” logice în interiorul textului de potrivire. La o căutare reușită, match.group(1) este textul care corespunde primei paranteze din stânga, iar match.group(2) este textul care corespunde celei de-a doua paranteze din stânga. În mod normal, match.group() simplu este tot textul de potrivire întreg, ca de obicei.
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)
Un flux de lucru obișnuit cu expresiile regulate este acela de a scrie un model pentru lucrul pe care îl căutați, adăugând grupuri de paranteze pentru a extrage părțile pe care le doriți.
findall
findall() este probabil cea mai puternică funcție din modulul re. Mai sus am folosit re.search() pentru a găsi prima potrivire pentru un model. findall() găsește *toate* potrivirile și le returnează sub forma unei liste de șiruri de caractere, fiecare șir reprezentând o potrivire.
## 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
Pentru fișiere, s-ar putea să aveți obiceiul de a scrie o buclă pentru a itera peste liniile fișierului și ați putea apoi apela findall() pe fiecare linie. În schimb, lăsați findall() să facă iterația pentru dumneavoastră — mult mai bine! Doar introduceți întregul text al fișierului în findall() și lăsați-l să returneze o listă cu toate corespondențele într-un singur pas (amintiți-vă că f.read() returnează întregul text al unui fișier într-un singur șir de caractere):
# 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 and Groups
Mecanismul de grupare prin paranteze ( ) poate fi combinat cu findall(). Dacă modelul include 2 sau mai multe grupuri de paranteze, atunci, în loc să returneze o listă de șiruri de caractere, findall() returnează o listă de *tuple*. Fiecare tupla reprezintă o potrivire a modelului, iar în interiorul tuplei se află datele group(1), group(2) …. Astfel, dacă la modelul de e-mail se adaugă 2 grupuri de paranteze, atunci findall() returnează o listă de tuple, fiecare cu lungimea 2, care conține numele de utilizator și gazda, de exemplu (‘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
După ce aveți lista de tuple, puteți face o buclă peste ea pentru a efectua un calcul pentru fiecare tuplu. Dacă modelul nu include nicio paranteză, atunci findall() returnează o listă de șiruri găsite, ca în exemplele anterioare. Dacă modelul include un singur set de paranteze, atunci findall() returnează o listă de șiruri de caractere care corespund acelui singur grup. (Caracteristică opțională obscură: Uneori aveți grupări de paranteze ( ) în model, dar pe care nu doriți să le extrageți. În acest caz, scrieți parantezele cu un ?: la început, de exemplu (?: ) și acea paranteză din stânga nu va conta ca rezultat al unui grup.)
RE Workflow and Debug
Modelele de expresii regulate împachetează o mulțime de semnificații în doar câteva caractere , dar sunt atât de dense, încât puteți petrece mult timp depanând modelele dumneavoastră. Configurați-vă timpul de execuție astfel încât să puteți rula un model și să imprimați cu ușurință ceea ce se potrivește, de exemplu, rulându-l pe un mic text de test și imprimând rezultatul lui findall(). Dacă modelul nu se potrivește cu nimic, încercați să slăbiți modelul, eliminând părți din el astfel încât să obțineți prea multe potriviri. Atunci când nu se potrivește nimic, nu puteți face niciun progres, deoarece nu există nimic concret la care să vă uitați. Odată ce se potrivește prea mult, atunci puteți lucra la strângerea incrementală a acestuia pentru a obține exact ceea ce doriți.
Opțiuni
Funcțiile re acceptă opțiuni pentru a modifica comportamentul potrivirii modelului. Stegulețul de opțiune este adăugat ca argument suplimentar la search() sau findall() etc., de exemplu re.search(pat, str, re.IGNORECASE).
- IGNORECASE — ignoră diferențele dintre majuscule și minuscule pentru potrivire, astfel încât „a” se potrivește atât cu „a” cât și cu „A”.
- DOTALL — permite ca punctul (.) să se potrivească cu linia nouă — în mod normal se potrivește cu orice altceva în afară de linia nouă. Acest lucru vă poate încurca — credeți că .* se potrivește cu totul, dar în mod implicit nu depășește sfârșitul unei linii. Rețineți că \s (spațiu alb) include liniile noi, astfel încât, dacă doriți să vă potriviți cu o serie de spații albe care pot include o linie nouă, puteți folosi \s*
- MULTILINE — Într-un șir format din mai multe linii, permiteți ca ^ și $ să se potrivească cu începutul și sfârșitul fiecărei linii. În mod normal, ^/$ s-ar potrivi doar cu începutul și sfârșitul întregului șir.
Greedy vs. Non-Greedy (opțional)
Aceasta este o secțiune opțională care prezintă o tehnică mai avansată de expresii regulate care nu este necesară pentru exerciții.
Supunem că aveți un text cu etichete în el: <b>foo</b> și <i>și așa mai departe</i>
Să presupunem că încercați să potriviți fiecare tag cu modelul „(<.*>)” — ce se potrivește mai întâi?
Rezultatul este puțin surprinzător, dar aspectul lacom al lui .* îl face să se potrivească cu întregul ‘<b>foo</b> și <i>și așa mai departe</i>’ ca o singură potrivire mare. Problema este că .* merge cât de departe poate, în loc să se oprească la primul > (adică este „lacomă”).
Există o extensie a expresiilor regulate în care se adaugă un ? la sfârșit, cum ar fi .*? sau .+?, ceea ce le schimbă pentru a nu fi lacome. Acum ele se opresc cât de repede pot. Astfel, modelul „(<.*?>)” va obține doar „<b>” ca primă potrivire, și „</b>” ca a doua potrivire, și așa mai departe, obținând fiecare pereche <..> pe rând. Stilul este, de obicei, să folosiți un .*? și apoi, imediat în dreapta sa, să căutați un marker concret (> în acest caz) care forțează sfârșitul execuției .*?.
Extensiunea *? își are originea în Perl, iar expresiile regulate care includ extensiile Perl sunt cunoscute sub numele de expresii regulate compatibile Perl — pcre. Python include suport pentru pcre. Multe utilitare de linie de comandă etc. au un indicator prin care acceptă modele pcre.
O tehnică mai veche, dar utilizată pe scară largă pentru a codifica această idee de „toate aceste caractere, cu excepția opririi la X” folosește stilul de paranteze pătrate. Pentru cele de mai sus ați putea scrie modelul, dar în loc de .* pentru a obține toate caracterele, folosiți * care sare peste toate caracterele care nu sunt > (^ de la început „inversează” setul de paranteze pătrate, astfel încât se potrivește cu orice caracter care nu se află în paranteze).
Substituire (opțional)
Funcția re.sub(pat, replacement, str) caută toate cazurile de model în șirul dat și le înlocuiește. Șirul de înlocuire poate include „\1”, „\2”, care se referă la textul din grupul(1), grupul(2), și așa mai departe din textul original de potrivire.
Iată un exemplu care caută toate adresele de e-mail și le schimbă pentru a păstra utilizatorul (\1), dar are ca gazdă 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
Exercițiu
Pentru a exersa expresiile regulate, consultați exercițiul Nume de copii.
.