2.35. Noțiuni de bază despre tipare

O expresie regulată este un mic limbaj pentru descrierea șirurilor după formă, mai degrabă decât după conținutul lor exact. Folosește-o atunci când un șir corespunde dacă și numai dacă urmează un tipar pe care îl poți descrie, dar nu îl poți enumera – „o secvență de cifre urmată de o unitate”, „o linie care începe cu ERROR și se termină cu un număr”, „oricare dintre aceste extensii de fișier, în orice ordine, cu prefixe v opționale”.

Apelează la re numai atunci când o metodă simplă de șir nu este suficientă.

Fiecare dintre acestea este mai rapidă, mai ușor de citit și mai greu de greșit decât regex-ul echivalent. Folosește regex atunci când contează forma șirului, iar subșirul exact nu contează.

2.35.1. Cele patru lucruri pe care le vei folosi

Modulul MicroPython re expune patru lucruri:

  • re.compile() – transformă un șir de tipar într-un obiect tipar compilat pe care îl poți reutiliza.

  • re.match() – încearcă tiparul la începutul unui șir. Tiparul este ancorat la poziția 0.

  • re.search() – încearcă tiparul oriunde într-un șir. Returnează prima potrivire.

  • re.sub() – găsește fiecare potrivire și o înlocuiește.

Omisiuni notabile față de CPython: niciun re.findall, niciun re.finditer, niciun re.split la nivel de modul (tiparele compilate au în schimb o metodă split), niciun re.fullmatch, nicio constantă de indicator precum re.IGNORECASE. Acolo unde ai apela una dintre acestea în CPython, construiește echivalentul din re.search() într-o buclă.

2.35.2. Un prim tipar

Tiparul r'\d+' potrivește una sau mai multe cifre:

>>> import re
>>> m = re.search(r'\d+', 'sensor reading 42 ok')
>>> m.group(0)
'42'

Câteva lucruri de observat:

  • Tiparul este scris ca un șir brut (r'...'), astfel încât bara oblică inversă din \d să ajungă la re în loc să fie procesată ca o secvență de escape a unui șir Python. Folosește întotdeauna șiruri brute pentru tiparele regex.

  • re.search() returnează un obiect de potrivire în caz de succes și None în caz de eșec. Verifică întotdeauna înainte de a apela match.group().

  • m.group(0) este textul complet pe care l-a potrivit tiparul. Grupurile 1, 2, … apar mai târziu, odată ce tiparul conține paranteze de captură.

Același tipar cu re.match() returnează None, deoarece șirul nu începe cu o cifră:

>>> re.match(r'\d+', 'sensor reading 42 ok') is None
True
>>> re.match(r'\d+', '42 readings')
<match num=1>

2.35.3. Componentele unui tipar

Cele mai utile tipare sunt construite dintr-un set mic de componente. Cele care funcționează în MicroPython:

Caractere literale – orice caracter care nu este special se potrivește pe sine. hello potrivește hello.

Caractere speciale. ^ $ * + ? { } [ ] \ | ( ) au toate semnificațiile de mai jos. Pentru a potrivi unul dintre ele literal, escapează-l cu o bară oblică inversă: \. potrivește un punct literal.

Clase de caractere – prescurtări pentru seturi de caractere frecvente:

  • \d – orice cifră 0-9

  • \D – orice caracter care nu este cifră

  • \s – orice caracter de spațiere (spațiu, tab, linie nouă)

  • \S – orice caracter care nu este de spațiere

  • \w – orice caracter de „cuvânt”: litere, cifre, liniuță de subliniere

  • \W – orice caracter care nu este de cuvânt

  • . – orice caracter, cu excepția liniei noi

Cuantificatori – de câte ori trebuie să se potrivească componenta anterioară:

  • * – zero sau mai multe (lacom)

  • + – unul sau mai multe (lacom)

  • ? – zero sau unul

  • {n} – exact n

  • {m,n} – între m și n (inclusiv)

Combinare: \d{3}-\d{4} potrivește trei cifre, o liniuță, patru cifre. \s+ potrivește unul sau mai multe caractere de spațiere. hello.*world potrivește hello, orice (inclusiv nimic), apoi world.

Notă

Lacom înseamnă că cuantificatorul consumă cât de mult posibil din intrare, lăsând totuși restul tiparului să se potrivească. Pe hello x world y world, .* din hello.*world potrivește cea mai lungă secvență care încă lasă un world la sfârșit – capturează x world y, nu mai scurtul x. Același lucru este valabil pentru + și forma de interval {m,n}: motorul ia cea mai lungă potrivire posibilă, apoi se retrage doar dacă restul tiparului eșuează.

2.35.4. Substituție

re.sub() găsește fiecare potrivire și o înlocuiește cu un șir. Înlocuirea poate face referire la grupurile capturate prin \1, \2, … (tratate mai târziu, împreună cu restul sintaxei de grup). Fără grupuri, re.sub este o simplă căutare-și-înlocuire pe un regex:

>>> re.sub(r'\s+', ' ', 'too    many   spaces')
'too many spaces'

>>> re.sub(r'\d+', 'N', 'log 12, log 345, log 6')
'log N, log N, log N'

Al treilea argument este șirul asupra căruia se operează; rezultatul este un șir nou cu fiecare potrivire înlocuită.

2.35.5. Împărțire – doar pe un tipar compilat

Nu există re.split la nivel de modul. Pentru a împărți pe baza unui regex, compilează mai întâi tiparul și apelează metoda sa split

>>> sep = re.compile(r'\s*,\s*')
>>> sep.split('a , b,c ,  d')
['a', 'b', 'c', 'd']

Al doilea argument opțional limitează numărul de împărțiri:

>>> sep.split('a, b, c, d', 2)
['a', 'b', 'c, d']

2.35.6. Compilarea pentru reutilizare

Dacă același tipar rulează de multe ori – în interiorul unei bucle sau într-o funcție intens folosită – compilează-l o singură dată și reutilizează obiectul compilat:

digit_run = re.compile(r'\d+')

def first_number(line):
    m = digit_run.search(line)
    return int(m.group(0)) if m else None

Apelarea metodelor pattern.match() și pattern.search() pe un obiect compilat este același lucru cu funcțiile la nivel de modul, dar evită costul recompilării la fiecare apel.

2.35.7. Tipare care nu potrivesc nimic

Trei tipare în special îi prind pe nepregătite pe dezvoltatori:

  • .* potrivește șirul gol. re.search(r'.*', s).group(0) returnează '' pe orice intrare.

  • Un tipar cu un caracter special neescapat este o eroare de sintaxă. re.compile(r'cost: $5') ridică o eroare ValueError, deoarece $ înseamnă „sfârșitul șirului”. Folosește r'cost: \$5'.

  • Punctul . nu potrivește o linie nouă. Pentru a potrivi peste liniile noi, scrie tiparul astfel încât să le trateze explicit cu [\s\S] sau furnizează câte o linie pe rând.

Cu aceste componente, un tipar poate potrivi aproape orice porțiune de text cu formă fixă. Extragerea înapoi a datelor structurate din potrivire necesită grupuri de captură.