Le espressioni regolari sono un potente linguaggio per abbinare modelli di testo. Questa pagina fornisce un’introduzione di base alle espressioni regolari sufficienti per i nostri esercizi Python e mostra come funzionano le espressioni regolari in Python. Il modulo “re” di Python fornisce il supporto alle espressioni regolari.

In Python una ricerca con espressione regolare è tipicamente scritta come:

 match = re.search(pat, str)

Il metodo re.search() prende un pattern di espressione regolare e una stringa e cerca quel pattern all’interno della stringa. Se la ricerca ha successo, search() restituisce un oggetto match o None altrimenti. Pertanto, la ricerca è di solito immediatamente seguita da un if-statement per verificare se la ricerca ha avuto successo, come mostrato nel seguente esempio che cerca il pattern ‘word:’ seguito da una parola di 3 lettere (dettagli sotto):

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'

Il codice match = re.search(pat, str) memorizza il risultato della ricerca in una variabile chiamata “match”. Poi l’if-statement testa il match — se è vero la ricerca è riuscita e match.group() è il testo corrispondente (per esempio ‘word:cat’). Altrimenti se match è false (None per essere più specifici), allora la ricerca non ha avuto successo, e non c’è testo corrispondente.

La ‘r’ all’inizio della stringa di pattern designa una stringa “grezza” di python che passa attraverso le backslashes senza modifiche, il che è molto utile per le espressioni regolari (Java ha bisogno di questa caratteristica!). Vi raccomando di scrivere sempre le stringhe di pattern con la ‘r’ solo per abitudine.

Schemi di base

La potenza delle espressioni regolari è che possono specificare schemi, non solo caratteri fissi. Qui ci sono i modelli di base che corrispondono a singoli caratteri:

  • a, X, 9, < — i caratteri ordinari corrispondono esattamente a se stessi. I meta-caratteri che non corrispondono a se stessi perché hanno significati speciali sono: . ^ $ * + ? { \ | ( ) (dettagli sotto)
  • . (un punto) — corrisponde a qualsiasi singolo carattere eccetto il newline ‘\n’
  • \w — (w minuscola) corrisponde a un carattere “parola”: una lettera o cifra o barra inferiore . Notate che sebbene “word” sia il mnemonico per questo, corrisponde solo ad un singolo carattere di parola, non ad una parola intera. \W (W maiuscola) corrisponde a qualsiasi carattere non parola.
  • \b — confine tra parola e non-parola
  • \s — (s minuscola) corrisponde ad un singolo carattere di spazio bianco — spazio, newline, return, tab, form . \S (S maiuscola) corrisponde a qualsiasi carattere non-spazio.
  • \t, \n, \r — tab, newline, return
  • \d — cifra decimale (alcune vecchie utility regex non supportano \d, ma tutte supportano \w e \s)
  • ^ = inizio, $ = fine — corrisponde all’inizio o alla fine della stringa
  • \ — inibisce la “specialità” di un carattere. Così, per esempio, usa \. per far corrispondere un punto o \ per far corrispondere una barra. Se non sei sicuro che un carattere abbia un significato speciale, come ‘@’, puoi mettere una barra davanti ad esso, \@, per assicurarti che sia trattato solo come un carattere.

Esempi di base

Scherzo: come si chiama un maiale con tre occhi? piiig!

Le regole di base della ricerca di espressioni regolari per un pattern all’interno di una stringa sono:

  • La ricerca procede attraverso la stringa dall’inizio alla fine, fermandosi alla prima corrispondenza trovata
  • Tutto il pattern deve essere abbinato, ma non tutta la stringa
  • Se match = re.search(pat, str) ha successo, match non è None e in particolare match.group() è il testo corrispondente
 ## 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"

Ripetizione

Le cose si fanno più interessanti quando si usano + e * per specificare la ripetizione nello schema

  • + — 1 o più occorrenze dello schema alla sua sinistra, per esempio ‘i+’ = una o più i
  • * — 0 o più occorrenze dello schema alla sua sinistra
  • ? — corrisponde a 0 o 1 occorrenza del motivo alla sua sinistra

Più a sinistra & Più grande

Prima la ricerca trova la corrispondenza più a sinistra del motivo, e poi cerca di usare il più possibile della stringa — cioè + e * vanno il più lontano possibile (il + e * sono detti “avidi”).

Esempi di ripetizione

 ## 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"

Esempio di email

Supponiamo di voler trovare l’indirizzo email dentro la stringa ‘xyz [email protected] purple monkey’. Useremo questo come esempio per dimostrare altre caratteristiche delle espressioni regolari. Ecco un tentativo usando il pattern r’\w+@\w+’:

 str = 'purple [email protected] monkey dishwasher' match = re.search(r'\w+@\w+', str) if match: print match.group() ## 'b@google'

La ricerca non ottiene l’intero indirizzo email in questo caso perché la \w non corrisponde al ‘-‘ o al ‘.’ nell’indirizzo. Risolveremo questo problema usando le caratteristiche dell’espressione regolare qui sotto.

Fra parentesi quadre

Le parentesi quadre possono essere usate per indicare un insieme di caratteri, quindi corrisponde ad ‘a’ o ‘b’ o ‘c’. Anche i codici \w, \s ecc. funzionano dentro le parentesi quadre con l’unica eccezione che il punto (.) significa solo un punto letterale. Per il problema delle email, le parentesi quadre sono un modo semplice per aggiungere ‘.’ e ‘-‘ all’insieme di caratteri che possono apparire intorno alla @ con lo schema r’+@+’ per ottenere l’intero indirizzo email:

 match = re.search(r'+@+', str) if match: print match.group() ## '[email protected]'

(Altre caratteristiche delle parentesi quadre) Puoi anche usare un trattino per indicare un intervallo, quindi corrisponde a tutte le lettere minuscole. Per usare un trattino senza indicare un intervallo, metti il trattino per ultimo, ad esempio . Un cappello in alto (^) all’inizio di un insieme di parentesi quadre lo inverte, quindi significa qualsiasi carattere eccetto ‘a’ o ‘b’.

Estrazione di gruppi

La caratteristica “gruppo” di un’espressione regolare ti permette di selezionare parti del testo corrispondente. Supponiamo, per il problema delle email, di voler estrarre separatamente il nome utente e l’host. Per farlo, aggiungete delle parentesi ( ) intorno al nome utente e all’host nel modello, come questo: r'(+)@(+)’. In questo caso, le parentesi non cambiano ciò a cui il pattern corrisponderà, ma stabiliscono dei “gruppi” logici all’interno del testo di corrispondenza. Se la ricerca ha successo, match.group(1) è il testo corrispondente alla prima parentesi sinistra, e match.group(2) è il testo corrispondente alla seconda parentesi sinistra. La semplice match.group() è ancora l’intero testo di corrispondenza come al solito.

 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 flusso di lavoro comune con le espressioni regolari è che si scrive un modello per la cosa che si sta cercando, aggiungendo gruppi di parentesi per estrarre le parti desiderate.

findall

findall() è probabilmente la singola funzione più potente nel modulo re. Sopra abbiamo usato re.search() per trovare la prima corrispondenza di uno schema. findall() trova *tutte* le corrispondenze e le restituisce come una lista di stringhe, dove ogni stringa rappresenta una corrispondenza.

 ## 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

Per i file, potreste avere l’abitudine di scrivere un ciclo per iterare le linee del file, e potreste poi chiamare findall() su ogni linea. Invece, lasciate che findall() faccia l’iterazione per voi — molto meglio! Basta inserire l’intero testo del file in findall() e lasciare che restituisca una lista di tutte le corrispondenze in un singolo passo (ricordate che f.read() restituisce l’intero testo di un file in una singola stringa):

 # 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 e gruppi

Il meccanismo dei gruppi di parentesi ( ) può essere combinato con findall(). Se lo schema include 2 o più gruppi di parentesi, allora invece di restituire una lista di stringhe, findall() restituisce una lista di *tuple*. Ogni tupla rappresenta una corrispondenza del pattern, e all’interno della tupla c’è il dato group(1), group(2) …. Quindi, se 2 gruppi di parentesi sono aggiunti al pattern dell’email, allora findall() restituisce una lista di tuple, ciascuna di lunghezza 2 contenente il nome utente e l’host, ad esempio (‘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

Una volta che avete la lista di tuple, potete fare un loop su di essa per fare qualche calcolo per ogni tupla. Se lo schema non include parentesi, allora findall() restituisce una lista di stringhe trovate come negli esempi precedenti. Se lo schema include un singolo gruppo di parentesi, allora findall() restituisce una lista di stringhe corrispondenti a quel singolo gruppo. (Caratteristica opzionale oscura: A volte avete raggruppamenti di parentesi ( ) nel pattern, ma che non volete estrarre. In questo caso, scrivete le parentesi con un ?: all’inizio, ad esempio (?: ) e quella parentesi sinistra non conterà come risultato del gruppo.)

RE Workflow and Debug

I pattern delle espressioni regolari racchiudono molto significato in pochi caratteri, ma sono così densi che potete passare molto tempo a fare il debug dei vostri pattern. Impostate il vostro runtime in modo da poter eseguire un pattern e stampare ciò che corrisponde facilmente, per esempio eseguendolo su un piccolo testo di prova e stampando il risultato di findall(). Se il pattern non corrisponde a nulla, provate a indebolire il pattern, rimuovendo parti di esso in modo da ottenere troppe corrispondenze. Quando non corrisponde a nulla, non potete fare alcun progresso poiché non c’è nulla di concreto da guardare. Una volta che corrisponde a troppe cose, allora puoi lavorare sul restringimento incrementale per ottenere proprio quello che vuoi.

Opzioni

Le funzioni re accettano opzioni per modificare il comportamento del pattern match. Il flag dell’opzione viene aggiunto come argomento extra a search() o findall() ecc., ad esempio re.search(pat, str, re.IGNORECASE).

  • IGNORECASE — ignora le differenze maiuscole/minuscole per la corrispondenza, quindi ‘a’ corrisponde sia ad ‘a’ che ad ‘A’.
  • DOTALL — permette che il punto (.) corrisponda ai newline — normalmente corrisponde a tutto tranne che ai newline. Questo può farvi inciampare — pensate che .* corrisponda a tutto, ma per default non va oltre la fine di una linea. Nota che \s (spazio bianco) include i newline, quindi se vuoi far corrispondere una serie di spazi bianchi che possono includere un newline, puoi semplicemente usare \s*
  • MULTILINE — All’interno di una stringa composta da molte linee, permetti a ^ e $ di corrispondere all’inizio e alla fine di ogni linea. Normalmente ^/$ corrisponderebbe solo all’inizio e alla fine dell’intera stringa.

Greedy vs. Non-Greedy (opzionale)

Questa è una sezione opzionale che mostra una tecnica di espressione regolare più avanzata non necessaria per gli esercizi.

Supponiamo di avere un testo con dei tag: <b>foo</b> e <i> così via</i>

Supponiamo che tu stia cercando di far corrispondere ogni tag con il pattern ‘(<.*>)’ — a cosa corrisponde per primo?

Il risultato è un po’ sorprendente, ma l’aspetto avido del .* fa sì che faccia corrispondere l’intero ‘<b>foo</b> e <i>così via</i>’ come una grande corrispondenza. Il problema è che il .* va fino a dove può, invece di fermarsi al primo > (cioè è “avido”).

C’è un’estensione alle espressioni regolari dove si aggiunge un ? alla fine, come .*? o .+?, cambiandole per essere non avide. Ora si fermano appena possono. Così il pattern ‘(<.*?>)’ otterrà solo ‘<b>’ come prima corrispondenza, e ‘</b>’ come seconda corrispondenza, e così via ottenendo ogni coppia <..> a turno. Lo stile è tipicamente quello di usare un .*?, e poi immediatamente alla sua destra cercare qualche marcatore concreto (> in questo caso) che forzi la fine dell’esecuzione del .*?

L’estensione *? è nata in Perl, e le espressioni regolari che includono le estensioni di Perl sono note come Perl Compatible Regular Expressions — pcre. Python include il supporto pcre. Molte utility della linea di comando ecc. hanno un flag in cui accettano pattern pcre.

Una tecnica più vecchia ma ampiamente usata per codificare questa idea di “tutti questi caratteri eccetto fermarsi a X” usa lo stile delle parentesi quadre. Per quanto sopra si potrebbe scrivere il pattern, ma invece di .* per ottenere tutti i caratteri, usare * che salta tutti i caratteri che non sono > (la ^ iniziale “inverte” il set di parentesi quadre, quindi corrisponde a qualsiasi carattere non tra le parentesi).

Sostituzione (opzionale)

La funzione re.sub(pat, replacement, str) cerca tutte le istanze del pattern nella stringa data, e le sostituisce. La stringa di sostituzione può includere ‘\1’, ‘\2’ che si riferiscono al testo del gruppo(1), gruppo(2), e così via dal testo originale corrispondente.

Ecco un esempio che cerca tutti gli indirizzi email, e li cambia per mantenere l’utente (\1) ma avere yo-yo-dyne.com come host.

 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

Esercizio

Per fare pratica con le espressioni regolari, vedi l’Esercizio sui nomi dei bambini.

admin

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.

lg