5.9. Introduzione alle espressioni regolari
Le espressioni regolari sono un strumento potente (e piuttosto standardizzato)
per cercare, sostituire o analizzare del testo contenente complessi schemi di
caratteri. Se avete già usato espressioni regolari in altri linguaggi
(come il Perl), potete saltare questa sezione e leggere direttamente il
sommario del modulo re per avere una panoramica delle funzioni
disponibili e dei loro argomenti.
I tipi stringa hanno i propri metodi di ricerca (index,
find e count),
di sostituzione (replace) e di analisi
(split), ma questi sono limitati
ai casi più semplici di utilizzo. Il metodi di ricerca operano con una singola
sotto-stringa dal valore predefinito e sono sempre sensibili alla differenza
tra maiuscole e minuscole; per effettuare ricerche in una stringa
s, volendo ignorare tale differenza, occorre chiamare i
metodi s.lower() o s.upper() ed essere
sicuri che la stringa da cercare abbia lo stesso tipo di carattere
(maiuscolo/minuscolo) di s. I metodi replace
e split hanno le stesse limitazioni. Laddove è
possibile conviene usarli (sono veloci e leggibili), ma per cose più complesse
è necessario passare alle espressioni regolari.
Esempio 5.19. Cercare corrispondenze alla fine di una stringa
Questa serie di esempi sono ispirati da un problema reale che ho
avuto durante il mio lavoro ufficiale, dove dovevo adattare e
standardizzare indirizzi stradali estratti da un sistema precedente,
prima di inserirli in un nuovo sistema (come vedete non mi invento
niente: queste cose sono utili realmente).
>>> s = '100 NORTH MAIN ROAD'
>>> s.replace('ROAD', 'RD.')
'100 NORTH MAIN RD.'
>>> s = '100 NORTH BROAD ROAD'
>>> s.replace('ROAD', 'RD.')
'100 NORTH BRD. RD.'
>>> s[:-4] + s[-4:].replace('ROAD', 'RD.')
'100 NORTH BROAD RD.'
>>> import re
>>> re.sub('ROAD$', 'RD.', s)
'100 NORTH BROAD RD.'
|
Il mio obiettivo è di standardizzare un indirizzo stradale in modo tale che
'ROAD' sia sempre abbreviato come 'RD.'.
A primo acchito, avevo considerato la cosa abbastanza semplice da poter usare
il metodo replace. Dopo tutto, i dati erano già tutti in
lettere maiuscole, in modo che la differenza tra maiuscolo e minuscolo non
sarebbe stata un problema. E la stringa di ricerca, 'ROAD',
era una costante. In effetti, in questo caso ingannevolmente
semplice, s.replace funziona come ci si aspetta.
|
|
La vita, sfortunatamente, è piena di controesempi, e scoprii presto
quanto ciò sia vero. Il problema qui è che 'ROAD'
appare due volte nell'indirizzo, una volta come parte del nome della
strada, 'BROAD', ed una volta come parola a sé
stante. Il metodo replace trova le due occorrenze
e ciecamente le sostituisce entrambe; in questo modo, il mio indirizzo
viene distrutto.
|
|
Per risolvere il problema di indirizzi con più di una sottostringa
'ROAD', potremmo ricorrere a qualcosa del genere:
cercare e sostituire 'ROAD' solo negli ultimi 4
caratteri dell'indirizzo (s[-4:]), e lasciare
intatto il resto della stringa (s[-4:]). Ma potete
vedere come la cosa stia già diventando poco gestibile. Per esempio,
questo schema dipende dalla lunghezza della stringa che vogliamo
sostituire (se volessimo sostituire 'STREET' con
'ST.', dovremmo usare s[:-6] e
s[-6:].replace(...)). Vi piacerebbe ritornare su
questo codice dopo sei mesi per cercarvi un “baco”?.
Io so che preferirei evitarlo.
|
|
È tempo di passare alle espressioni regolari. In Python,
tutte le funzioni collegate alle espressioni regolari sono contenute
nel modulo re.
|
|
Date un'occhiata al primo parametro 'ROAD$'. Si
tratta di una espressione regolare molto semplice, che corrisponde
a 'ROAD' solo quando questa sottostringa si trova
alla fine. Il carattere $ significa “fine
della stringa”. Esiste un carattere corrispondente, il
carattere ^, che significa “inizio della
stringa”.
|
|
Usando la funzione re.sub, si cerca nella stringa
s per sottostringhe corrispondenti all'espressione
regolare 'ROAD$' e le si rimpiazza con
'RD.'. In questo modo si rimpiazza la sottostringa
ROAD alla fine della stringa s,
ma non quella che è parte della parola BROAD,
perché questa è in mezzo alla stringa s.
|
Esempio 5.20. Cercare la corrispondenza con parole complete
>>> s = '100 BROAD'
>>> re.sub('ROAD$', 'RD.', s)
'100 BRD.'
>>> re.sub('\\bROAD$', 'RD.', s)
'100 BROAD'
>>> re.sub(r'\bROAD$', 'RD.', s)
'100 BROAD'
>>> s = '100 BROAD ROAD APT. 3'
>>> re.sub(r'\bROAD$', 'RD.', s)
'100 BROAD ROAD APT. 3'
>>> re.sub(r'\bROAD\b', 'RD.', s)
'100 BROAD RD. APT 3'
|
Continuando con la mia storia degli indirizzi da aggiustare,
scoprii presto che l'esempio precedente, in cui cercavo
corrispondenze con 'ROAD' alla fine della
stringa, non era abbastanza efficiente perché non tutti gli
indirizzi includevano una designazione del tipo di strada; alcuni
terminavano con il solo nome della strada. Il più delle volte,
questo mi andava bene, ma se il nome della strada era
'BROAD', allora l'espressione regolare avrebbe
corrisposto con 'ROAD' alla fine della stringa,
anche se questi era parte della parola 'BROAD',
e questo non era quello che volevo.
|
|
Quello che realmente volevo era di trovare
corrispondenze con 'ROAD' quando si trovava
alla fine della stringa ed era una parola
a se stante, non una parte di una parola più grande. Per esprimere
questo con una espressione regolare, occorre usare il codice
\b, che significa “in questo punto ci
deve essere un limite alla parola”. In Python, l'uso di
questo codice è complicato dal fatto che per inserire il carattere
'\' in una stringa, esso deve essere preceduto
da un altro carattere '\' (“escaped”
ndt). A questo fatto si fa qualche volta riferimento come alla
“piaga del backslash” (backslash == barra inversa, ndt),
e questa è una delle ragioni per cui le espressioni regolari sono
più facili in Perl che in Python. D'altra parte, Perl mischia le
espressioni regolari con altre notazioni sintattiche, per cui se c'è
un “baco” può essere difficile stabilire se esso sia
nell'espressione regolare o negli altri componenti sintattici.
|
|
Per evitare la piaga del backslash, si possono usare le cosiddette
stringe grezze, facendo precedere il '...' con
la lettera r (raw cioè grezzo,
ndt). Questo dice a Python che niente nella stringa deve essere
interpretato come carattere speciale. La stringa '\t'
indica di solito il carattere di tabulazione, ma la stringa
r'\t' è proprio il carattere barra inversa
\ seguito dalla lettera t. Io di
solito raccomando di usare sempre stringe grezze quando si ha a che fare
con le espressioni regolari, altrimenti le cose diventano subito troppo
confuse ( e le espressioni regolari già di per se fanno presto a diventare
poco chiare).
|
|
*sigh* Sfortunatamente, scoprii presto che
molti casi concreti contraddicevano la mia logica. In questo caso,
l'indirizzo conteneva la parola 'ROAD' come parola
a se stante, ma non era alla fine, perché l'indirizzo includeva il
numero di un appartamento dopo la specifica della strada. Dato che
'ROAD' non è alla fine della stringa, non
corrisponde, e quindi la chiamata a re.sub
finisce per non sostituire proprio niente, e noi ci ritroviamo con
la stringa originale, che non è quello che volevamo.
|
|
Per risolvere questo problema, ho rimosso il carattere $
ed ho aggiunto un'altro \b. Ora l'espressione regolare
si legge: “corrisponde a 'ROAD' quando è una
parola a se stante in qualunque punto della stringa”, sia all'inizio
che alla fine che da qualche parte nel mezzo.
|
Questo rappresenta appena la punta estrema dell'iceberg rispetto a
quello che le espressioni regolari possono fare. Si tratta di uno
strumento estremamente potente e vi sono interi libri a loro
dedicate. Non sono però la soluzione migliore per ogni problema.
È importante saperne abbastanza da capire quando sono adeguate
e quando invece possono causare più problemi di quanti ne risolvano.
|
Alcune persone, quando affrontano un problema, pensano: “Lo so,
finirò per usare le espressioni regolari”. A questo punto,
hanno due problemi.
|
|
--
Jamie Zawinski, in comp.lang.emacs
|
|