Python - Regular Expressions (Italiano)

Ciao là, futuri maghi Python! Oggi, intraprenderemo un viaggio avventuroso nel mondo delle Espressioni Regolari (regex) in Python. Non preoccuparti se non hai mai sentito parlare di regex prima: alla fine di questo tutorial, sarai in grado di utilizzare questo potente strumento come un professionista!

Python - Reg Expressions

Cos'sono le Espressioni Regolari?

Prima di immergerci, capiamo cos'sono le espressioni regolari. Immagina di essere un detective che cerca un pattern specifico in un mare di testo. Le espressioni regolari sono come la tua lente di ingrandimento, aiutandoti a cercare e manipolare stringhe in base a pattern. Cool, vero?

Stringhe Raw

In Python, quando lavori con regex, spesso utilizzi stringhe raw. Queste sono precedute da un 'r' e trattano i backslash come caratteri letterali. Questo è particolarmente utile nelle regex, dato che i backslash sono comuni.

# Stringa normale
print("Ciao\nMondo")
# Stringa raw
print(r"Ciao\nMondo")

Nel primo caso, vedrai "Ciao" e "Mondo" su righe separate. Nel secondo, vedrai "Ciao\nMondo" così com'è. Questo diventa cruciale quando si lavora con pattern regex.

Metacaratteri

I metacaratteri sono i mattoni fondamentali delle regex. Hanno un significato speciale e ci aiutano a definire i pattern. Ecco alcuni comuni:

Metacarattere Significato
. Corrisponde a qualsiasi carattere tranne il newline
^ Corrisponde all'inizio di una stringa
$ Corrisponde alla fine di una stringa
* Corrisponde a 0 o più ripetizioni
+ Corrisponde a 1 o più ripetizioni
? Corrisponde a 0 o 1 ripetizione
{} Corrisponde a un numero specificato di ripetizioni
[] Specifica un set di caratteri da corrispondere
\ Esca i caratteri speciali

La funzione re.match()

La funzione re.match() tenta di trovare una corrispondenza all'inizio di una stringa. Se trova una corrispondenza, restituisce un oggetto di corrispondenza; altrimenti, restituisce None.

import re

risultato = re.match(r"Ciao", "Ciao, Mondo!")
if risultato:
print("Corrispondenza trovata:", risultato.group())
else:
print("Nessuna corrispondenza")

Questo stampa "Corrispondenza trovata: Ciao". Il metodo group() restituisce la sottostringa corrispondente.

La funzione re.search()

Mentre re.match() cerca una corrispondenza all'inizio di una stringa, re.search() scansiona l'intera stringa per trovare una corrispondenza.

import re

risultato = re.search(r"Mondo", "Ciao, Mondo!")
if risultato:
print("Corrispondenza trovata:", risultato.group())
else:
print("Nessuna corrispondenza")

Questo stampa "Corrispondenza trovata: Mondo".

Confronto tra Match e Search

La principale differenza tra match() e search() è che match() cerca una corrispondenza solo all'inizio della stringa, mentre search() cerca una corrispondenza ovunque nella stringa.

La funzione re.findall()

La funzione re.findall() restituisce tutte le corrispondenze non sovrapposte di un pattern in una stringa come una lista.

import re

testo = "La pioggia in Spagna cade principalmente nella pianura"
risultato = re.findall(r"ain", testo)
print(risultato)

Questo stampa ['ain', 'ain', 'ain'].

La funzione re.sub()

La funzione re.sub() sostituisce tutte le occorrenze di un pattern in una stringa con una stringa di sostituzione.

import re

testo = "La pioggia in Spagna"
risultato = re.sub(r"a", "o", testo)
print(risultato)

Questo stampa "La piooggio in Spogna".

La funzione re.compile()

La funzione re.compile() crea un oggetto regex per il riutilizzo, che può essere più efficiente se stai usando lo stesso pattern più volte.

import re

pattern = re.compile(r"\d+")
risultato1 = pattern.findall("Ci sono 123 mele e 456 arance")
risultato2 = pattern.findall("Ho 789 banane")

print(risultato1)
print(risultato2)

Questo stampa ['123', '456'] e ['789'].

La funzione re.finditer()

La funzione re.finditer() restituisce un iteratore che produce oggetti di corrispondenza per tutte le corrispondenze non sovrapposte di un pattern in una stringa.

import re

testo = "La pioggia in Spagna"
for corrispondenza in re.finditer(r"ain", testo):
print(f"Trovato '{corrispondenza.group()}' alla posizione {corrispondenza.start()}-{corrispondenza.end()}")

Questo stampa:

Trovato 'ain' alla posizione 5-8
Trovato 'ain' alla posizione 17-20

Casi d'uso delle Regex in Python

Le espressioni regolari hanno numerose applicazioni pratiche. Guardiamo un caso d'uso comune:

Trovare parole che iniziano con vocali

import re

testo = "Un'aquila al giorno tira il dottore giù"
parole_con_vocali = re.findall(r'\b[aeiouAEIOU]\w+', testo)
print(parole_con_vocali)

Questo stampa ['Un'aquila', 'al', 'a', 'giù'].

Modificatori delle Espressioni Regolari: Opzioni di Flag

Il modulo re di Python fornisce diversi flag di opzione che modificano come i pattern sono interpretati:

Flag Descrizione
re.IGNORECASE (re.I) Esegue una corrispondenza case-insensitive
re.MULTILINE (re.M) Fa sì che ^ corrisponda all'inizio di ogni riga e $ alla fine di ogni riga
re.DOTALL (re.S) Fa sì che . corrisponda a qualsiasi carattere, inclusi newline
re.VERBOSE (re.X) Permette di scrivere pattern regex più leggibili

Pattern delle Espressioni Regolari

Esploriamo alcuni pattern più avanzati:

Classi di caratteri

Le classi di caratteri permettono di specificare un set di caratteri da corrispondere:

import re

testo = "Il volatile brown fox salta sopra il cane pigro"
risultato = re.findall(r"[aeiou]", testo)
print(risultato)

Questo stampa tutte le vocali trovate nel testo.

Classi di Caratteri Speciali

Python regex supporta classi di caratteri speciali:

Classe Descrizione
\d Corrisponde a qualsiasi cifra decimale
\D Corrisponde a qualsiasi carattere non numerico
\s Corrisponde a qualsiasi carattere di spaziatura
\S Corrisponde a qualsiasi carattere non di spaziatura
\w Corrisponde a qualsiasi carattere alfanumerico
\W Corrisponde a qualsiasi carattere non alfanumerico

Casi di Ripetizione

Possiamo specificare quante volte un pattern dovrebbe comparire:

import re

testo = "Ho 111 mele e 22 arance"
risultato = re.findall(r"\d{2,3}", testo)
print(risultato)

Questo stampa ['111', '22'], corrispondendo a numeri con 2 o 3 cifre.

Ripetizione non greedy

Per default, la ripetizione è greedy, ossia corrisponde quante più volte possibile. Aggiungendo un ? dopo la ripetizione la rende non greedy:

import re

testo = "<h1>Titolo</h1><p>Paragrafo</p>"
greedy = re.findall(r"<.*>", testo)
non_greedy = re.findall(r"<.*?>", testo)
print("Greedy:", greedy)
print("Non-greedy:", non_greedy)

Questo mostrerà la differenza tra matching greedy e non greedy.

Gruppo con Parentesi

Le parentesi permettono di raggruppare parti della regex:

import re

testo = "John Smith ([email protected])"
risultato = re.search(r"(\w+) (\w+) \((\w+@\w+\.\w+)\)", testo)
if risultato:
print(f"Nome Completo: {risultato.group(1)} {risultato.group(2)}")
print(f"Email: {risultato.group(3)}")

Questo estrae il nome e l'email dal testo.

Backreference

Le backreference permettono di fare riferimento a gruppi corrispondenti precedentemente:

import re

testo = "<h1>Titolo</h1><p>Paragrafo</p>"
risultato = re.findall(r"<(\w+)>.*?</\1>", testo)
print(risultato)

Questo corrisponde a tag HTML di apertura e chiusura.

Alternative

Il carattere | permette di specificare alternative:

import re

testo = "Il colore del cielo è blu o grigio"
risultato = re.search(r"blu|grigio", testo)
if risultato:
print(f"Trovato colore: {risultato.group()}")

Questo corrisponde a "blu" o "grigio".

Ancore

Le ancore specificano posizioni nel testo:

import re

testo = "Python è fantastico"
inizio = re.match(r"^Python", testo)
fine = re.search(r"fantastico$", testo)
print(f"Comincia con Python: {bool(inizio)}")
print(f"Termina con fantastico: {bool(fine)}")

Questo verifica se il testo inizia con "Python" e finisce con "fantastico".

Sintassi Speciale con Parentesi

Le parentesi possono essere usate per più che solo raggruppare:

  • (?:...) crea un gruppo non catturante
  • (?P...) crea un gruppo con nome
  • (?=...) crea un lookahead positivo
  • (?!...) crea un lookahead negativo
import re

testo = "Python versione 3.9.5"
risultato = re.search(r"Python (?:versione )?(?P<versione>\d+\.\d+\.\d+)", testo)
if risultato:
print(f"Versione: {risultato.group('versione')}")

Questo estrae il numero di versione, indipendentemente dal fatto che "versione" sia presente o meno nel testo.

Eccoci qua, ragazzi! Abbiamo viaggiato attraverso la terra delle regex in Python, dai concetti di base ai concetti più avanzati. Ricorda, come ogni strumento potente, le regex richiedono pratica per essere padroneggiate. Quindi, non essere scoraggiato se all'inizio sembra complicato. Continua a sperimentare, e presto sarai in grado di trovare pattern come un detective professionista! Buon coding!

Credits: Image by storyset