Espressioni regolari

Indice

Definizione di espressione regolare

Operazione di matching

Operazione di searching

Accesso alla matching/searching occurrence

Come scrivere un'espressione regolare

Due esempi di matching

Tre esempi di searching

Backreference esterno

Backreference interno

La funzione re.findall() per trovare tutte le occorrenze

La funzione re.sub() per effettuare sostituzioni

Definizione di espressione regolare

Un'espressione regolare RE è una stringa di simboli che rappresenta un linguaggio (cioé un insieme di stringhe) e non se stessa.

Es: ca?t se intesa come RE rappresenta l'insieme di due stringhe {ct, cat}, ma non la stringa ca?t. Oppure ca*t se intesa come RE rappresenta l'insieme di tutte le stringhe composte dal simbolo c seguito da 0 o più simboli a, seguiti dal simbolo t. Invece ca+t rappresenta tutte le stringhe di ca*t tranne ct.

Un'espressione regolare viene utilizzata in Python per compiere:

  • operazioni di matching
  • operazioni di searching
  • operazioni di sostituzione

Per utilizzare un'espressione regolare si deve importare il modulo re

    import re

In [2]:
import re

Operazione di matching

Definizione

Data una stringa S e un'espressione regolare RE, l'operazione di matching verifica se S inizia o coincide con una delle stringhe rappresentate da RE.

Matching occurrence

L'operazione di matching ha un funzionamento greedy: considera infatti tutti i prefissi di S, a partire da quello di lunghezza massima (che coincide con S) e via via considerando quelli di lunghezza decrescente. La matching occurrence è il primo prefisso incontrato che appartiene al linguaggio di RE.

Funzione re.match()

re.match(my_expr, my_string)

restituisce un oggetto di tipo re.Match che contiene tutte le informazioni relative all'operazione di matching tra la RE my_expr e la stringa my_string.


In [8]:
m = re.match('cat', 'dog and cat')
print(m)


None

Da notare che la RE cat rappresenta l'unica stringa cat (cioé la RE rappresenta se stessa). Nell'esempio precedente l'oggetto restituito dall'operazione di matching è nullo in quanto cat non occorre come prefisso in dog and cat.

Negli esempi successivi l'oggetto restituito dall'operazione di matching non è nullo. Nel secondo caso la matching occurrence coincide con l'intera stringa.


In [92]:
re.match('cat', 'cat and dog')


Out[92]:
<re.Match object; span=(0, 3), match='cat'>

In [10]:
re.match('cat', 'cat')


Out[10]:
<re.Match object; span=(0, 3), match='cat'>

Operazione di searching

Definizione

Data una stringa S e un'espressione regolare RE, l'operazione di searching verifica se in S esiste un'occorrenza di una delle stringhe del linguaggio di RE.

Searching occurrence

L'operazione di searching ha un funzionamento greedy: la stringa S viene scandita da sinistra a destra (dal primo all’ultimo carattere). Per ogni posizione, vengono considerate tutte le sottostringhe che iniziano in quella posizione, a partire da quella di lunghezza massima (cioè si parte da un suffisso di S). La searching occurrence è la prima sottostringa incontrata che appartiene al linguaggio di RE.

Funzione re.search()

re.search(my_expr, my_string)

restituisce un oggetto di tipo re.Match che contiene tutte le informazioni relative all'operazione di searching della RE my_expr nella stringa my_string.


In [3]:
re.search('cat', 'dog and cat')


Out[3]:
<re.Match object; span=(8, 11), match='cat'>

In [4]:
re.search('cat', 'cat and dog')


Out[4]:
<re.Match object; span=(0, 3), match='cat'>

In [5]:
re.search('cat', 'cat')


Out[5]:
<re.Match object; span=(0, 3), match='cat'>

Accesso alla matching/searching occurrence

L'oggetto di tipo re.Match restituito da un'operazione di matching/searching mette a disposizione due metodi che permettono di ottenere la matching/searching occurrence:

  • Il metodo start() restituisce la posizione di inizio (0-based) della prima occorrenza trovata.

  • Il metodo end() restituisce la posizione (0-based) successiva a quella di fine della prima occorrenza trovata.

NOTA BENE: in un'operazione di matching start() restituisce sempre 0 (ovviamente se l'oggetto restituito dall'operazione non è nullo).


In [25]:
s = re.search('cat', 'dog and cat and rat')

In [26]:
s.start()


Out[26]:
8

In [27]:
s.end()


Out[27]:
11

Per ottenere la matching occurrence basta accedere alla stringa dog and cat and rat


In [28]:
'dog and cat and rat'[s.start():s.end()]


Out[28]:
'cat'

Come scrivere un'espressione regolare

  • I simboli . | ( ) [ ] { } + \ ^ $ * ? nelle RE sono metasimboli e rappresentano qualcosa di diverso da se stessi. Per fare in modo che rappresentino se stessi occorre anteporre un carattere di backslash \. Ad esempio \? rappresenta il punto di domanda, e quindi la RE ca\?t rappresenta l'unica stringa ca?t.

  • Tutti i simboli diversi dai metasimboli precedenti rappresentano se stessi. Diventano metasimboli se gli si antepone un \. Ad esempio \A è il metasimbolo che specifica l'inizio di stringa (elemento di dimensione nulla prima del primo carattere della stringa).

I metasimboli permettono di specificare i seguenti elementi:

  • ancore
  • classi
  • quantificatori
  • alternative
  • raggruppamenti
  • backreference interni

Ancora

L'ancora è un elemento di dimensione nulla che può rappresentare:

  • inizio riga ^

Es: la RE ^cat rappresenta l'unica stringa cat con il vincolo che sia a inizio riga, e quindi occorre nelle stringhe cataaaa e aaaa\ncataaaa ma non in aaacataaa, aaacat e aaaacat\naaaa.

  • fine riga $

Es: la RE cat$ rappresenta l'unica stringa cat con il vincolo che sia a fine riga, e quindi occorre nelle stringhe aaacat e aaaacat\naaaa ma non in aaacataaa, cataaa e aaaa\ncataaaa.

  • inizio stringa \A

Es: la RE \Acat rappresenta l'unica stringa cat con il vincolo che sia a inizio stringa, e quindi occorre nella stringa cataaaa ma non in aaaa\ncataaaa.

  • fine stringa \z

Es: la RE cat\z rappresenta l'unica stringa cat con il vincolo che sia a fine stringa, e quindi occorre nella stringa aaaacat ma non in aaaacat\naaaa e aaaa\naaaacat\n.

  • fine stringa \Z (eventualmente prima di \n)

Es: la RE cat\Z rappresenta l'unica stringa cat con il vincolo che sia a fine stringa (anche prima di \n), e quindi occorre nella stringa aaaacat e aaaa\naaaacat\n ma non in aaaacat\naaaa.

  • confine di parola \b

Un simbolo di parola è un una lettera minuscola da a a z, oppure una lettera maiuscola da A a Z, oppure una cifra da 0 a 9 oppure simbolo di underscore _.

  • confine di parola: elemento di dimensione nulla tra un simbolo di parola e un simbolo non di parola.

Es: la RE \bis rappresenta l'unica stringa is con il vincolo che prima di i non ci sia un simbolo di parola, e quindi occorre nella stringa It is a cat ma non in This cat.

  • non confine di parola \B (negazione di \b)

Es: la RE \Bis rappresenta l'unica stringa is con il vincolo che prima di i ci sia un simbolo di parola, e quindi occorre nella stringa This cat ma non in It is a cat.


Classe

Una classe è un insieme di caratteri, e viene specificata tra due parentesi quadre [] in uno dei seguenti modi:

  • elencando ogni carattere appartenente alla classe

Es: [aeiou] rappresenta la classe delle vocali minuscole. [.;:,] rappresenta la classe dei simboli di punteggiatura (in questo caso il simbolo . non è un metasimbolo). [?\b] rappresenta la classe dei due simboli ? e backspace.

  • specificando intervalli tramite il simbolo -.

Es: [a-z] è la classe delle lettere minuscole. [a-zA-Z] è la classe di tutte le lettere. [a-zA-Z0-9_] è la classe di tutti i simboli di parola.

NOTA BENE: [a\-z] è la classe dei tre simboli a, - e z.

Il simbolo ^ messo subito dopo la prima parentesi [ permette di effettuare la negazione di ciò che viene specificato dopo.

Es: [^aeiou] è la classe di tutto ciò che non è vocale minuscola. [^a-zA-Z0-9_] è la classe di tutti i simboli che non sono di parola.

NOTA BENE: [ae^iou] è la classe delle vocali minuscole e del simbolo ^.

Una classe rappresenta ognuno dei simboli che le appartengono.

Es: la RE [A-Z]at rappresenta le stringhe Cat, Rat e Bat ma non le stringhe cat, rat e bat (che invece sono rappresentate dalle RE [a-z]at e [a-zA-Z]at.

Per alcune classi esiste un metasimbolo scorciatoia:

  • \d equivalente alla classe [0-9] (cifra decimale)
  • \D equivalente alla classe per[^0-9] (negazione di \d)
  • \w equivalente alla classe [a-zA-Z0-9_] (simbolo di parola)
  • \W equivalente alla classe [^a-zA-Z0-9_] (negazione di \w)
  • \h equivalente alla classe [0-9a-fA-F] (cifra esadecimale)
  • \H equivalente alla classe [^0-9a-fA-F] (negazione di \h)
  • \s equivalente alla classe [␣\t\r\n\f] (tutto ciò che è spazio)
  • \S equivalente alla classe [^␣\t\r\n\f] (negazione di \s)
  • . equivalente alla classe [^\n] (qualsiasi carattere eccetto \n)

Raggruppamento

Un raggruppamento è una parte di RE specificato all'interno di parentesi tonde () che può:

  • essere sottoposto a quantificazione
  • essere sottoposto a backreference esterno in operazioni di matching, searching o sostituzione
  • variare la precedenza delle alternative

Es: la RE a(bc)d contiene il raggruppamento (bc).


Quantificatore

Un quantificatore specifica il numero di volte con cui il carattere, la classe o il raggruppamento che lo precedono possono manifestarsi all'interno del stringa con cui la RE viene confrontata. Un quantificatore può specificare:

  • zero o più ripetizioni *

Es: la RE ca*t rappresenta le stringhe ct e cat, caat, caaat, etc., cioé le stringhe composte da c, seguita da zero o più simboli a, seguiti da t.

  • una o più ripetizioni +

Es: la RE ca+t rappresenta le stringhe cat, caat, caaat, etc., ma non ct, cioé le stringhe composte da c, seguita da uno o più simboli a, seguiti da t.

Es: La RE c[ab]+t rappresenta tutte le stringhe composte da un simbolo c seguito da una o più ripetizioni del simbolo a oppure b seguite dal simbolo t. Quindi ad esempio la stringa caabbbabababbat appartiene al linguaggio della RE.

Es: La RE c(ab)+t rappresenta le stringhe cabt, cababt, cabababt, etc., cioé le stringhe composte da c, seguita da una o più ripetizioni di ab, seguite da t.

  • zero o una ripetizione ?

Es: la RE ca?t rappresenta le sole stringhe ct e cat.

  • da m a n ripetizioni {m,n}

Es: la RE ca{2,5}t rappresenta le sole stringhe caat, caaat, caaaat e caaaaat, composte da c, seguita da due, o quattro, o cinque simboli a, seguiti da t.

  • almeno m ripetizioni {m,}

Es: la RE ca{2,}t rappresenta le stringhe caat, caaat, caaaat, caaaaat, etc., composte da c, seguita da almeno due a, seguiti da t.

  • al più n ripetizioni {,n}

Es: la RE ca{,5}t rappresenta le sole stringhe ct, cat, caat caaat, caaaat e caaaaat, composte da c, seguita da al più cinque simboli a, seguiti da t.

  • esattamente m ripetizioni {m}

Es: la RE ca{5}t rappresenta la sola stringa caaaaat, composte da c, seguita da cinque simboli a, seguiti da t.


Alternativa

Un'alternativa specifica la possibilità tra due parti della RE, e viene specificata tramite il metasimbolo |.

Es: la RE ab|cd rappresenta il linguaggio {ab, cd}.

NOTA BENE: il simbolo | viene valutato per ultimo e quindi la RE cane nero|bianco corrisponde al linguaggio {cane nero, bianco} e non al linguaggio {cane nero, cane bianco} . La RE che corrisponde al linguaggio {cane nero, cane bianco} è invece cane (nero|bianco).


Backreference interno

Il backreference interno crea la connessione tra diverse parti della RE.

I metasimboli \\1, \\2, \\3 etc., specificano una connessione con i raggruppamenti presenti nella RE da usare in operazioni di matching, searching o sostituzione. Precisamente \\1 si riferisce al primo raggruppamento a partire da sinistra, \\2 si riferisce al secondo, etc.

Es: nella RE (\w+)\s\\1 è presente unicamente il metasimbolo \\1 che si riferisce all'unico raggruppamento presente.

Due esempi di searching

Esempio 1

Si consideri la stringa:


In [104]:
stringa = '***hello world***'

Si effettui il searching della RE \w+ che rappresenta tutte le stringhe composte da uno o più simboli di parola.


In [105]:
s = re.search('\w+', stringa)

La searching occurrence è:


In [106]:
stringa[s.start():s.end()]


Out[106]:
'hello'

Se si vuole fare in modo che la searching occurrence sia hello world si deve operare il searching con la RE \w+\s\w+ che rappresenta tutte le stringhe composte da uno o più simboli di parola seguiti da uno spazio e poi ancora da uno o più simboli di parola.


In [107]:
s = re.search('\w+\s\w+', stringa)

La searching occurrence è ora:


In [108]:
stringa[s.start():s.end()]


Out[108]:
'hello world'

Si cambi ora la stringa in ***hello world*** mantenendo la stessa RE.


In [109]:
stringa = '***hello     world***'
s = re.search('\w+\s\w+', stringa)

L'operazione di searching restituisce ora un oggetto nullo.


In [110]:
stringa[s.start():s.end()]


---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-110-2bc36a8634ed> in <module>
----> 1 stringa[s.start():s.end()]

AttributeError: 'NoneType' object has no attribute 'start'

La RE che permette di catturare hello world (con un qualsiasi numero di spazi tra hello e world) è \w+\s+\w+.


In [111]:
s = re.search('\w+\s+\w+', stringa)

La searching occurrence è infatti:


In [112]:
stringa[s.start():s.end()]


Out[112]:
'hello     world'

Si provi ora a usare la RE .+ che rappresenta tutte le stringhe di uno o più caratteri qualsiasi (tranne il newline \n).


In [113]:
s = re.search('.+', stringa)

La searching occurrence è ora l'intera stringa.


In [114]:
stringa[s.start():s.end()]


Out[114]:
'***hello     world***'

A questo punto si inserisca nella stringa un carattere di newline \n dopo hello e si operi il searching della RE .+.


In [115]:
stringa = '***hello\n     world***'
s = re.search('.+', stringa)

La searching occurrence diventa:


In [116]:
stringa[s.start():s.end()]


Out[116]:
'***hello'

in quanto il simbolo \n non appartiene alla classe rappresentata dal metasimbolo ..

Esempio2

Si consideri la stringa bbbcaaaaaaaatcaaaat.


In [117]:
stringa = 'bbbcaaaaaaaatcaaaat'

Si effettui il searching della RE (ca)+ che rappresenta tutte le stringhe composte da ca ripetuto almeno una volta.


In [118]:
s = re.search('(ca)+', stringa)

La searching occurrence è:


In [119]:
stringa[s.start():s.end()]


Out[119]:
'ca'

che inizia in posizione:


In [120]:
s.start()


Out[120]:
3

e finisce in posizione:


In [121]:
s.end()


Out[121]:
5

Si tolgano ora le parentesi tonde () e si effettui il searching della RE ca+ che rappresenta tutte le stringhe composte da c seguita da una o più a.


In [122]:
s = re.search('ca+', stringa)

La searching occurrence è:


In [123]:
stringa[s.start():s.end()]


Out[123]:
'caaaaaaaa'

che inizia in posizione:


In [124]:
s.start()


Out[124]:
3

e finisce in posizione:


In [125]:
s.end()


Out[125]:
12

La searching occurrence, a causa del comportamento greedy dell'operazione, si estende il più a destra possibile. Per annullare questo comportamento e limitare al minimo la lunghezza, basta inserire il metasimbolo ? subito dopo il quantificatore +.


In [78]:
s = re.search('ca+?', stringa)

La searching occurrence è ora:


In [79]:
stringa[s.start():s.end()]


Out[79]:
'ca'

Si provi ora a sostituire il quantificatore + con * e a usare quindi la RE ca* che rappresenta tutte le stringhe composte da c seguita da zero o più a.


In [80]:
s = re.search('ca*', stringa)

La searching occurrence è:


In [81]:
stringa[s.start():s.end()]


Out[81]:
'caaaaaaaa'

Si aggiunga un ? subito dopo il quantificatore *.


In [82]:
s = re.search('ca*?', stringa)

La searching occurrence diventa:


In [83]:
stringa[s.start():s.end()]


Out[83]:
'c'

Backreference esterno

Il meccanismo di backreference esterno permette di catturare parti della searching/matching occurrence e di usarle all'esterno dell'operazione di matching/searching.

Requisito*: la RE deve contenere i raggruppamenti delle parti da catturare. I raggruppamenti sono indicizzati da sinistra a destra a partire da 1 (0 è l’indice di default dell’intera matching/searching occurrence).

il metodo group() degli oggetti di tipo re.Match restituisce la parte di matching/searching occurrence relativa al raggruppamento il cui indice viene specificato come argomento. Se group() viene invocato senza argomento, allora assume che l’indice sia 0 (quello di default) e quindi restituisce l'intera matching/searching occurrence.

NOTA BENE: anche i metodi start() ed end() possono prendere come argomento l'indice di un raggruppamento, e in tale caso restituiscono lo start e l'end della parte catturata.

La RE dell'esempio seguente contiene due raggruppamenti.


In [126]:
s = re.search('(\w+)\s+(\w+)', 'gatto cane')

La searching occurrence è:


In [127]:
s.group()


Out[127]:
'gatto cane'

La parte catturata dal primo raggruppamento è:


In [128]:
s.group(1)


Out[128]:
'gatto'

e inizia in posizione:


In [129]:
s.start(1)


Out[129]:
0

La parte catturata dal secondo raggruppamento è:


In [130]:
s.group(2)


Out[130]:
'cane'

e inizia in posizione:


In [131]:
s.start(2)


Out[131]:
6

Backreference interno

Il meccanismo di backreference interno permette di creare un riferimento interno ai raggruppamenti della RE tramite i metasimboli \\1, \\2, \\3etc., dove \\isi riferisce all'i-esimo raggruppamento a partire da sinistra.

Ad esempio la RE (\w+)\s+\\1 rappresenta tutte le stringhe composte da uno o più caratteri di parola, seguiti da uno o più spazi, seguiti dalla stessa stringa che il primo raggruppamento cattura nell'operazione di searching. Quindi (\w+)\s+\\1 occorre nella stringa gatto gatto ma non nella stringa gatto cane:


In [132]:
s = re.search('(\w+)\s+\\1', 'gatto gatto')

La searching occurrence è:


In [133]:
s.group(1)


Out[133]:
'gatto'

Se invece si usa la stringa gatto cane:


In [136]:
s = re.search('(\w+)\s+\\1', 'gatto cane')

In [137]:
s.group(1)


---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-137-7add5676537c> in <module>
----> 1 s.group(1)

AttributeError: 'NoneType' object has no attribute 'group'

Altro esempio:


In [7]:
s = re.search('(\w+)\\1', 'Mississippi')

La searching occurrence è:


In [8]:
s.group()


Out[8]:
'ississ'

La funzione re.findall() per trovare tutte le occorrenze

La funzione:

re.findall(my_expr, my_string)

trova tutte le occorrenze non sovrapposte della RE my_expr nella stringa my_string, e restituisce:

  • la lista delle occorrenze elencate da sinistra a destra, se nella RE non sono presenti raggruppamenti
  • la lista delle occorrenze catturate da un raggruppamento, se nella RE è presente un solo raggruppamento
  • la lista delle occorrenze catturate dai raggruppamenti, organizzati in tuple, se nella RE sono presenti più raggruppamenti (anche annidati)

In [20]:
re.findall('\w\w', 'abcdefgh')


Out[20]:
['ab', 'cd', 'ef', 'gh']

In [21]:
re.findall('(\w)\w', 'abcdefgh')


Out[21]:
['a', 'c', 'e', 'g']

In [22]:
re.findall('(\w)(\w)', 'abcdefgh')


Out[22]:
[('a', 'b'), ('c', 'd'), ('e', 'f'), ('g', 'h')]

In [23]:
re.findall('((\w)(\w))', 'abcdefgh')


Out[23]:
[('ab', 'a', 'b'), ('cd', 'c', 'd'), ('ef', 'e', 'f'), ('gh', 'g', 'h')]

Il seguente esempio mostra come separare una stringa usando come delimitatore tutto ciò che non è simbolo di parola.


In [92]:
re.findall('\w+', 'cat dog mouse pig')


Out[92]:
['cat', 'dog', 'mouse', 'pig']

Il seguente esempio mostra come separare una stringa usando come delimitatore tutto ciò che non è simbolo di parola e ammettendo uno spazio tra due parole.


In [93]:
re.findall('\w+\s+\w+', 'cat dog mouse pig')


Out[93]:
['cat dog', 'mouse pig']

Si aggiungano ora dei raggruppamenti.


In [94]:
re.findall('(\w+)\s+\w+', 'cat dog mouse pig')


Out[94]:
['cat', 'mouse']

In [95]:
re.findall('(\w+)\s+(\w+)', 'cat dog mouse pig')


Out[95]:
[('cat', 'dog'), ('mouse', 'pig')]

La funzione re.sub() per effettuare sostituzioni

La funzione:

re.sub(my_expr, r_string, my_string)

restituisce la stringa ottenuta sostituendo con r_string tutte le occorrenze non sovrapposte di my_expr in my_string.


In [145]:
re.sub('\w+\s\w+', 'goose', 'cat dog mouse pig')


Out[145]:
'goose goose'