Le espressioni regolari sono un linguaggio efficace per trovare pattern di testo corrispondenti. Questa pagina fornisce un'introduzione di base alle espressioni regolari stesse, sufficiente per i nostri esercizi Python, e mostra come funzionano le espressioni regolari in Python. Il Python "re" fornisce il supporto per le espressioni regolari.
In Python, una ricerca di espressioni regolari viene generalmente scritta come segue:
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 esito positivo, search() restituisce un oggetto di corrispondenza, altrimenti restituisce None. Pertanto, la ricerca è solitamente seguita immediatamente da un'istruzione if per verificare se la ricerca è riuscita, come mostrato nell'esempio seguente che cerca il pattern "parola:" seguita da una parola di 3 lettere (dettagli sotto):
import re str = 'an example word:cat!!' match = re.search(r'word:\w\w\w', str) # If-statement after search() tests if it succeeded if match: print('found', match.group()) ## 'found word:cat' else: print('did not find')
Il codice match = re.search(pat, str)
memorizza il risultato di ricerca in una variabile denominata "match". Quindi l'istruzione if verifica la corrispondenza: se true, la ricerca è riuscita e match.group() è il testo corrispondente (ad es. "parola:cat"). Altrimenti, se la corrispondenza è falsa (nessuna per essere più specifici), la ricerca non è riuscita e non esiste un testo corrispondente.
La "r" all'inizio di una stringa con pattern indica un pitone "grezzo" stringa che passa per barre rovesciate senza alcuna modifica ed è molto utile per le espressioni regolari (Java non serve a utilizzare questa funzione). Ti consiglio di scrivere sempre stringhe con pattern proprio come un'abitudine.
Motivi di base
Il vantaggio delle espressioni regolari sta nel fatto che sono in grado di specificare pattern, non solo caratteri fissi. Di seguito sono riportati i pattern principali che corrispondono a caratteri singoli:
- a, X, 9, < I caratteri normali corrispondono esattamente a se stessi. I metacaratteri che non corrispondono a se stessi perché hanno significati speciali sono: ^ $ * + ? { [ ] \ | ( ) (dettagli di seguito)
- . (un punto) -- corrisponde a qualsiasi carattere singolo tranne la nuova riga '\n'
- \w -- (w minuscola) trova una "parola" carattere: una lettera o un numero o una sottolineatura [a-zA-Z0-9_]. Tieni presente che, sebbene "parola" è il termine mnemonico, trova solo un carattere, non una parola intera. \W (W maiuscola) trova qualsiasi carattere non descrittivo.
- \b -- confine tra parola e non parola
- \s -- (s minuscola) corrisponde a un singolo spazio vuoto: spazio, nuova riga, invio, tab, modulo [ \n\r\t\f]. \S (S maiuscola) trova qualsiasi carattere che non sia uno spazio vuoto.
- \t, \n, \r -- tab, nuova riga, ritorno
- \d -- cifra decimale [0-9] (alcune utilità precedenti dell'espressione regolare non supportano \d, ma supportano tutte \w e \s)
- ^ = inizio, $ = fine -- corrisponde all'inizio o alla fine della stringa
- \ -- inibire lo "specialità" un carattere. Quindi, ad esempio, utilizza \. per trovare una corrispondenza con un punto o \\ per corrispondere a una barra. Se non hai la certezza che un carattere abbia un significato speciale, ad esempio "@", puoi provare a inserire una barra davanti, \@. Se non è una sequenza di escape valida, come \c, il programma Python si arresta con un errore.
Esempi di base
Barzelletta: come si chiama un maiale con tre occhi? piii!
Le regole di base della ricerca di espressioni regolari per un pattern all'interno di una stringa sono:
- La ricerca procede sulla stringa dall'inizio alla fine, fermandosi alla prima corrispondenza trovata
- Deve corrispondere tutto il pattern, ma non tutta la stringa
- Se
match = re.search(pat, str)
ha esito positivo, la corrispondenza 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"
Ripetizioni
Le cose diventano più interessanti quando usi + e * per specificare la ripetizione nel pattern
- + -- 1 o più occorrenze del pattern alla sua sinistra, ad esempio 'i+' = una o più i
- * -- 0 o più occorrenze del pattern alla sua sinistra
- ? -- corrispondenza di 0 o 1 occorrenze del pattern alla sua sinistra
Più a sinistra e Più grande
Innanzitutto, la ricerca trova la corrispondenza più a sinistra per il pattern, poi cerca di utilizzare il più possibile la stringa, ad esempio + e * vanno il più lontano possibile (+ e * vengono definiti "greedy").
Esempi di ripetizioni
## 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 all'interno della stringa "xyz alice-b@google.com scimmia viola". Lo utilizzeremo come esempio per dimostrare altre caratteristiche delle espressioni regolari. Ecco un tentativo utilizzando il pattern r'\w+@\w+':
str = 'purple alice-b@google.com monkey dishwasher' match = re.search(r'\w+@\w+', str) if match: print(match.group()) ## 'b@google'
In questo caso la ricerca non restituisce l'intero indirizzo email perché \w non corrisponde al segno "-" o "." nell'indirizzo. Lo risolveremo utilizzando le funzioni delle espressioni regolari riportate di seguito.
Staffe quadrate
Le parentesi quadre possono essere utilizzate per indicare un insieme di caratteri, quindi [abc] corrisponde a "a" o "b" o "c". I codici \w, \s ecc. funzionano anche all'interno delle parentesi quadre, con l'unica eccezione che il punto (.) indica semplicemente un punto letterale. Per il problema delle email, le parentesi quadre rappresentano un modo semplice per aggiungere "." e "-" all'insieme di caratteri che possono apparire attorno a @ con il pattern r'[\w.-]+@[\w.-]+' per ottenere l'indirizzo email completo:
match = re.search(r'[\w.-]+@[\w.-]+', str) if match: print(match.group()) ## 'alice-b@google.com'
Estrazione gruppo
Il "gruppo" di un'espressione regolare consente di selezionare parti del testo corrispondente. Supponiamo, per il problema di e-mail, di voler estrarre il nome utente e l'host separatamente. Per farlo, racchiudi il nome utente e l'host tra parentesi ( ) in un pattern in questo modo: r'([\w.-]+)@([\w.-]+)'. In questo caso, le parentesi non cambiano la corrispondenza del pattern, bensì stabiliscono "gruppi" logici. all'interno del testo della corrispondenza. In una ricerca andata a buon fine, match.group(1) è il testo corrispondente alla prima parentesi di sinistra, mentre match.group(2) è il testo corrispondente alla seconda parentesi di sinistra. Il semplice match.group() è l'intero testo della corrispondenza, come al solito.
str = 'purple alice-b@google.com monkey dishwasher' match = re.search(r'([\w.-]+)@([\w.-]+)', str) if match: print(match.group()) ## 'alice-b@google.com' (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 consiste nello scrivere uno schema per ciò che stai cercando, aggiungendo gruppi di parentesi per estrarre le parti desiderate.
Trova tutto
Findall() è probabilmente la funzione più potente del modulo Re. Sopra abbiamo utilizzato re.search() per trovare la prima corrispondenza di un pattern. Findall() trova *all* le corrispondenze e le restituisce come un elenco di stringhe, dove ogni stringa rappresenta una corrispondenza.## Suppose we have a text with many email addresses str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher' ## Here re.findall() returns a list of all the found email strings emails = re.findall(r'[\w\.-]+@[\w\.-]+', str) ## ['alice@google.com', 'bob@abc.com'] for email in emails: # do something with each found email string print(email)
trova all'interno di Files
Per i file, potresti aver l'abitudine di scrivere un ciclo per iterare sulle righe del file, per poi chiamare searchall() su ogni riga. Invece, lascia che Findall() esegua l'iterazione al posto tuo: molto meglio! Inserisci l'intero testo del file in Findall() e lascia che restituisca un elenco di tutte le corrispondenze in un unico passaggio (ricorda che f.read() restituisce l'intero testo di un file in una singola stringa):
# Open file f = open('test.txt', encoding='utf-8') # 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 di gruppo delle parentesi ( ) può essere combinato con Findall(). Se il pattern include due o più gruppi di parentesi, invece di restituire un elenco di stringhe, la funzione Findall() restituisce un elenco di *tuples*. Ogni tupla rappresenta una corrispondenza del pattern e all'interno della tupla ci sono i dati group(1), group(2) e così via. Perciò, se al pattern email vengono aggiunti 2 gruppi di parentesi, la funzione Findall() restituisce un elenco di tuple, ciascuna delle quali contiene 2 nomi utente e host, ad esempio ("alice", "google.com").
str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher' tuples = re.findall(r'([\w\.-]+)@([\w\.-]+)', str) print(tuples) ## [('alice', 'google.com'), ('bob', 'abc.com')] for tuple in tuples: print(tuple[0]) ## username print(tuple[1]) ## host
Una volta ottenuto l'elenco delle tuple, puoi eseguirne il loop per eseguire un calcolo per ciascuna tupla. Se il pattern non include parentesi, il metodo searchall() restituisce un elenco di stringhe trovate, come indicato negli esempi precedenti. Se il pattern include una singola serie di parentesi, il metodo Findall() restituisce un elenco di stringhe corrispondenti a quel singolo gruppo. (Caratteristica facoltativa di difficile comprensione: a volte sono presenti parentesi ( ) raggruppamenti nel pattern, ma che non si desidera estrarre. In questo caso, scrivi le parentesi con ?: all'inizio, ad esempio (?: ) e la parentesi aperta non verrà conteggiata come un risultato di gruppo.
Flusso di lavoro RE e debug
I pattern di espressioni regolari racchiudono molto significato in pochi caratteri, ma sono così compatti che potresti dedicare molto tempo al debug dei tuoi pattern. Configura il runtime in modo da poter eseguire un pattern e stampare facilmente ciò che corrisponde, ad esempio eseguendolo su un piccolo testo di prova e stampando il risultato di Findall(). Se il pattern non corrisponde a nulla, prova a indebolire il pattern, rimuovendone parti in modo da ottenere troppe corrispondenze. Quando non corrisponde a nulla, non puoi fare progressi poiché non c'è nulla di concreto da esaminare. Una volta trovata una corrispondenza eccessiva, puoi lavorare per aumentarla in modo incrementale per raggiungere solo ciò che vuoi.
Opzioni
Le nuove funzioni prendono opzioni per modificare il comportamento della corrispondenza di pattern. Il flag di opzione viene aggiunto come argomento aggiuntivo a search() o Findall() e così via, ad es. re.search(pat, str, re.IGNORECASE).
- IGNORECASE -- ignora le differenze maiuscole/minuscole per la corrispondenza, quindi "a" corrisponde sia a "a" e "A".
- DOTALL: consente il collegamento tra il punto (.) e la nuova riga; in genere corrisponde a qualsiasi cosa tranne nuova riga. Questo può provocare un errore: pensi che .* corrisponda a tutto, ma per impostazione predefinita non supera la fine di una riga. Tieni presente che \s (spazio vuoto) include le nuove righe, quindi se vuoi trovare corrispondenze con un run di spazi vuoti che potrebbe includere una nuova riga, puoi semplicemente usare \s*
- MULTILINE: all'interno di una stringa composta da molte linee, consenti a ^ e $ di corrispondere all'inizio e alla fine di ogni riga. Normalmente ^/$ corrisponde solo all'inizio e alla fine dell'intera stringa.
Greedy e non greedy (facoltativo)
Questa è una sezione facoltativa che mostra una tecnica di espressione regolare più avanzata che non è necessaria per gli esercizi.
Supponi di avere del testo con tag: <b>foo</b> e <i>così via</i>
Supponiamo che tu stia tentando di far corrispondere ogni tag con il pattern "(<.*>)" -- Qual è la prima corrispondenza tra le due opzioni?
Il risultato è un po' sorprendente, ma l'aspetto greedy di .* fa corrispondere l'intero "<b>foo</b> e <i>così via</i>" un grande incontro. Il problema è che l'asterisco .* arriva il più lontano possibile, invece di fermarsi al primo > (ovvero "greedy").
Esiste un'estensione all'espressione regolare in cui aggiungi un ? alla fine, ad esempio .*? o .+?, modificandole in modo che non siano avide. Ora si fermano appena possibile. Quindi il pattern "(<.*?>)" riceverà solo "<b>" come prima corrispondenza e "</b>" come seconda corrispondenza e così via ottenendo ogni <..> si accoppiano a turno. In genere utilizzi uno stile .*? immediatamente seguito da un indicatore di cemento (in questo caso >) a cui .*? dell'esecuzione viene forzata a estendersi.
Il simbolo *? un'estensione nata da Perl, mentre le espressioni regolari che includono le estensioni di Perl sono note come espressioni regolari compatibili con Perl - pcre. Python include il supporto PCre. Molti util della riga di comando ecc. hanno un flag in cui accettano i pattern pcre.
Una tecnica antica ma ampiamente utilizzata per programmare questa idea di "tutti questi caratteri tranne che si ferma alla X" utilizza lo stile delle parentesi quadrate. In questo caso è possibile scrivere il pattern, ma invece di .* per ottenere tutti i caratteri, utilizza noindex>]*, che salta tutti i caratteri che non sono > (il carattere ^ iniziale "inverte" la parentesi quadra impostata, quindi corrisponde a qualsiasi carattere non incluso nelle parentesi).
Sostituzione (facoltativo)
La funzione re.sub(pat, replace, str) cerca tutte le istanze di pattern nella stringa specificata e le sostituisce. La stringa sostitutiva può includere '\1', '\2' che si riferiscono al testo dei gruppi(1), del gruppo(2) e così via del testo corrispondente originale.
Ecco un esempio che cerca tutti gli indirizzi email e li modifica per mantenere l'utente (\1) ma avere yo-yo-dyne.com come host.
str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher' ## re.sub(pat, replacement, str) -- returns new string with all replacements, ## \1 is group(1), \2 group(2) in the replacement print(re.sub(r'([\w\.-]+)@([\w\.-]+)', r'\1@yo-yo-dyne.com', str)) ## purple alice@yo-yo-dyne.com, blah monkey bob@yo-yo-dyne.com blah dishwasher
Esercizio
Per esercitarti con le espressioni regolari, vedi l'Esercizio Nomi bambini.