Introduzione
I dizionari Python sono strutture dati ideali per rappresentare associazioni tra una chiave e un valore. Un caso pratico molto comune è la gestione dei ruoli utente e dei relativi permessi in un’applicazione.
Per esempio, un sistema potrebbe prevedere i ruoli admin, editor e viewer. Ogni ruolo permette di eseguire operazioni differenti: creare contenuti, modificare dati, visualizzare informazioni o gestire altri utenti.
Invece di scrivere molte condizioni if sparse nel programma, possiamo raccogliere le regole in un dizionario. In questo modo il codice diventa più leggibile, facile da modificare e semplice da estendere.
In questo tutorial costruiremo un piccolo sistema per:
- definire i permessi associati a ogni ruolo;
- verificare se un utente possiede un determinato permesso;
- aggiungere e rimuovere utenti;
- gestire ruoli inesistenti in modo sicuro;
- produrre un riepilogo leggibile delle autorizzazioni.
Codice completo
# Dizionario che associa ogni ruolo all´insieme dei suoi permessi.
RUOLI = {
"admin": {
"visualizza_report",
"modifica_articoli",
"pubblica_articoli",
"gestisci_utenti"
},
"editor": {
"visualizza_report",
"modifica_articoli",
"pubblica_articoli"
},
"viewer": {
"visualizza_report"
}
}
# Dizionario degli utenti.
# La chiave è l´identificativo dell´utente,
# il valore è il suo ruolo.
UTENTI = {
"anna": "admin",
"marco": "editor",
"lucia": "viewer"
}
def ruolo_esistente(ruolo):
"""Restituisce True se il ruolo è definito nel sistema."""
return ruolo in RUOLI
def ha_permesso(username, permesso):
"""Verifica se un utente può eseguire una determinata operazione."""
ruolo = UTENTI.get(username)
# L´utente non esiste.
if ruolo is None:
return False
# Il ruolo non è valido: configurazione incoerente.
if ruolo not in RUOLI:
return False
return permesso in RUOLI[ruolo]
def aggiungi_utente(username, ruolo):
"""Aggiunge un utente solo se il ruolo è valido."""
if not ruolo_esistente(ruolo):
raise ValueError(f"Ruolo non valido: {ruolo}")
UTENTI[username] = ruolo
def cambia_ruolo(username, nuovo_ruolo):
"""Modifica il ruolo di un utente esistente."""
if username not in UTENTI:
raise KeyError(f"Utente inesistente: {username}")
if not ruolo_esistente(nuovo_ruolo):
raise ValueError(f"Ruolo non valido: {nuovo_ruolo}")
UTENTI[username] = nuovo_ruolo
def riepilogo_utente(username):
"""Restituisce una descrizione dei permessi dell´utente."""
ruolo = UTENTI.get(username)
if ruolo is None:
return f"L´utente ´{username}´ non esiste."
permessi = RUOLI.get(ruolo, set())
if not permessi:
return f"L´utente ´{username}´ ha un ruolo senza permessi."
elenco = ", ".join(sorted(permessi))
return (
f"Utente: {username}n"
f"Ruolo: {ruolo}n"
f"Permessi: {elenco}"
)
# Esempi di controllo dei permessi.
print(ha_permesso("anna", "gestisci_utenti"))
print(ha_permesso("marco", "gestisci_utenti"))
print(ha_permesso("lucia", "visualizza_report"))
print(ha_permesso("sconosciuto", "visualizza_report"))
# Aggiunta di un nuovo utente.
aggiungi_utente("paolo", "viewer")
# Modifica del ruolo di un utente.
cambia_ruolo("paolo", "editor")
# Stampa del riepilogo.
print(riepilogo_utente("paolo")) Spiegazione
Un dizionario di dizionari logico
Nel programma utilizziamo due dizionari distinti. Il primo, RUOLI, collega il nome di un ruolo a un insieme di permessi:
RUOLI["editor"] Il risultato è un set contenente le autorizzazioni dell’editor. Un insieme è particolarmente adatto perché un permesso non dovrebbe comparire più volte e il controllo di appartenenza è molto naturale:
"pubblica_articoli" in RUOLI["editor"] Il secondo dizionario, UTENTI, collega invece il nome dell’utente al suo ruolo. Per ottenere il ruolo di un utente usiamo get():
ruolo = UTENTI.get("marco") Se la chiave non esiste, get() restituisce None invece di sollevare un’eccezione KeyError. Questo comportamento è utile quando l’assenza dell’utente è una situazione prevista.
Perché usare una funzione di controllo
La funzione ha_permesso() nasconde i dettagli della struttura dati. Il resto dell’applicazione non deve sapere come sono organizzati i ruoli: deve soltanto chiedere se una certa operazione è consentita.
Questa separazione migliora la manutenzione. Se in futuro i permessi verranno caricati da un file JSON o da un database, sarà possibile modificare l’implementazione interna senza cambiare tutte le parti del programma che effettuano i controlli.
Aggiunta e modifica degli utenti
Le funzioni aggiungi_utente() e cambia_ruolo() verificano sempre che il ruolo esista. Senza questo controllo potremmo inserire dati incoerenti, ad esempio un utente con ruolo "super_editor" non definito in RUOLI.
Usiamo ValueError quando il valore fornito non è valido e KeyError quando si cerca di modificare un utente inesistente. Distinguere gli errori rende più semplice gestire correttamente i problemi nel codice chiamante.
Best practice
- Preferire costanti descrittive: nomi come
RUOLIeUTENTIrendono chiaro lo scopo dei dizionari. - Usare i set per i permessi: i set evitano duplicati e rendono leggibile il controllo con l’operatore
in. - Validare i dati in ingresso: ogni nuovo ruolo deve essere controllato prima di essere assegnato.
- Incapsulare la logica: funzioni come
ha_permesso()evitano di ripetere l’accesso diretto ai dizionari. - Usare
get()quando l’assenza è normale: è preferibile a un accesso diretto se la chiave potrebbe non esistere. - Non affidarsi soltanto al client: in un’applicazione web i permessi devono essere verificati anche sul server.
- Separare dati e codice: in un progetto reale, ruoli e utenti potrebbero essere caricati da un database o da un file di configurazione.
È inoltre consigliabile evitare di modificare direttamente i dizionari da molte parti del programma. Centralizzare le operazioni in funzioni o classi riduce il rischio di introdurre dati non validi.
Riepilogo
I dizionari permettono di modellare in modo semplice ed efficace un sistema di ruoli e autorizzazioni. Un dizionario può associare un utente al proprio ruolo, mentre un secondo dizionario può collegare ogni ruolo all’insieme dei permessi disponibili.
Questa soluzione è adatta a prototipi, script amministrativi e applicazioni di piccole dimensioni. I concetti fondamentali da ricordare sono:
- le chiavi dei dizionari devono identificare in modo univoco gli elementi;
get()aiuta a gestire le chiavi mancanti;- i set sono utili per rappresentare collezioni di autorizzazioni;
- la validazione impedisce di creare configurazioni incoerenti;
- le funzioni nascondono la complessità e migliorano la riusabilità.
