Introduzione
In Python, variabili e operatori non servono soltanto per eseguire calcoli matematici o confrontare valori. Un caso pratico e molto utile consiste nel rappresentare più opzioni all’interno di un singolo numero usando gli operatori bit a bit, chiamati anche operatori bitwise.
Questa tecnica è comune nella gestione di:
- permessi utente;
- funzionalità attivabili o disattivabili;
- stati di un dispositivo;
- opzioni di configurazione;
- proprietà combinate di un oggetto.
Ogni numero intero può essere rappresentato in forma binaria, cioè come una sequenza di bit che valgono 0 oppure 1. Possiamo assegnare ogni bit a una specifica caratteristica. Per esempio, il primo bit può rappresentare il permesso di lettura, il secondo quello di scrittura e il terzo quello di esecuzione.
In questo modo, una sola variabile può contenere più informazioni indipendenti. Gli operatori principali sono:
- |: OR bit a bit, usato per aggiungere un flag;
- &: AND bit a bit, usato per verificare un flag;
- ~: NOT bit a bit, usato per invertire i bit;
- ^: XOR bit a bit, usato per attivare o disattivare un flag;
- <<: spostamento a sinistra;
- >>: spostamento a destra.
Codice completo
from enum import IntFlag
class Permesso(IntFlag):
# Ogni valore occupa una posizione binaria diversa.
LETTURA = 1 << 0 # 0001, valore 1
SCRITTURA = 1 << 1 # 0010, valore 2
ELIMINAZIONE = 1 << 2 # 0100, valore 4
CONDIVISIONE = 1 << 3 # 1000, valore 8
def descrivi_permessi(permessi: Permesso) -> str:
"""Restituisce un testo con i permessi attivi."""
nomi = []
if permessi & Permesso.LETTURA:
nomi.append("lettura")
if permessi & Permesso.SCRITTURA:
nomi.append("scrittura")
if permessi & Permesso.ELIMINAZIONE:
nomi.append("eliminazione")
if permessi & Permesso.CONDIVISIONE:
nomi.append("condivisione")
if not nomi:
return "nessun permesso"
return ", ".join(nomi)
# Variabile iniziale: l´utente può leggere e scrivere.
permessi = Permesso.LETTURA | Permesso.SCRITTURA
print("Permessi iniziali:", descrivi_permessi(permessi))
# Aggiungiamo il permesso di condivisione.
permessi |= Permesso.CONDIVISIONE
print("Dopo l´aggiunta:", descrivi_permessi(permessi))
# Verifichiamo se l´utente può scrivere.
puo_scrivere = bool(permessi & Permesso.SCRITTURA)
print("Può scrivere?", puo_scrivere)
# Rimuoviamo il permesso di scrittura.
permessi &= ~Permesso.SCRITTURA
print("Dopo la rimozione:", descrivi_permessi(permessi))
# Invertiamo il permesso di eliminazione:
# se è assente viene aggiunto, se è presente viene rimosso.
permessi ^= Permesso.ELIMINAZIONE
print("Dopo XOR:", descrivi_permessi(permessi)) Spiegazione
Creare valori indipendenti con lo spostamento dei bit
L´espressione 1 << 0 produce il valore binario 0001, mentre 1 << 1 produce 0010. Spostando il valore 1 verso sinistra, si ottengono potenze di due:
- 1 << 0 equivale a 1;
- 1 << 1 equivale a 2;
- 1 << 2 equivale a 4;
- 1 << 3 equivale a 8.
Questi valori possono essere combinati senza sovrapporsi. Per esempio, l´espressione:
permessi = Permesso.LETTURA | Permesso.SCRITTURA combina i valori 0001 e 0010, ottenendo 0011. La variabile contiene quindi due permessi contemporaneamente.
Aggiungere un´opzione con OR
L´operatore | unisce i bit attivi dei suoi operandi. L´assegnamento composto |= è utile per aggiornare direttamente una variabile:
permessi |= Permesso.CONDIVISIONE Questa istruzione aggiunge il flag senza modificare quelli già presenti. Se il flag era già attivo, rimane attivo e non viene creato un valore duplicato.
Verificare un´opzione con AND
Per controllare se un flag è attivo, si usa l´operatore &. Se il risultato è diverso da zero, il flag è presente:
if permessi & Permesso.SCRITTURA:
print("Operazione consentita") La funzione bool() rende esplicita la conversione del risultato in un valore booleano. In Python, infatti, zero è considerato falso e qualsiasi valore intero diverso da zero è considerato vero.
Rimuovere un´opzione con AND e NOT
Per disattivare un flag, si usa una combinazione di & e ~:
permessi &= ~Permesso.SCRITTURA L´operatore ~ inverte i bit della maschera. Applicando poi l´AND, tutti i bit rimangono invariati tranne quello associato al permesso di scrittura, che viene impostato a zero.
Alternare un´opzione con XOR
L´operatore ^ esegue lo XOR. Quando il bit della maschera è 1, il bit corrispondente viene invertito. Per questo motivo è adatto a un pulsante o a un comando di tipo “attiva/disattiva”:
permessi ^= Permesso.ELIMINAZIONE Se il permesso era assente, viene aggiunto. Se era già presente, viene rimosso.
Perché usare IntFlag?
La classe IntFlag del modulo enum rende il codice più leggibile rispetto all´uso diretto di numeri come 1, 2, 4 e 8. I nomi descrivono il significato dei valori e riducono il rischio di errori.
Inoltre, un oggetto IntFlag conserva il comportamento numerico dei valori interi, ma permette di scrivere espressioni più comprensibili:
Permesso.LETTURA | Permesso.SCRITTURA è molto più chiaro di:
1 | 2 Best practice
- Usa potenze di due. Ogni flag deve occupare un solo bit. Le espressioni con 1 << posizione rendono questa scelta evidente.
- Preferisci IntFlag ai numeri anonimi. I nomi migliorano leggibilità, manutenzione e autocompletamento negli editor.
- Non confondere & con and. & lavora sui bit, mentre and valuta condizioni logiche complete.
- Non usare XOR per una rimozione definitiva. XOR alterna lo stato; se vuoi essere certo di rimuovere un flag, usa &= ~flag.
- Raggruppa i flag correlati. Una classe separata per permessi, impostazioni grafiche o stati di connessione evita configurazioni poco chiare.
- Converti esplicitamente in bool quando serve. Scrivere bool(valore & flag) comunica chiaramente che il risultato atteso è vero o falso.
- Documenta il significato dei bit. In progetti complessi, commenti e nomi descrittivi aiutano chi dovrà modificare il codice.
Riepilogo
Gli operatori bit a bit permettono di memorizzare più flag all´interno di una singola variabile intera. Con | si aggiungono opzioni, con & si verificano, con & ~ si rimuovono e con ^ si alternano.
Lo spostamento a sinistra, espresso con <<, aiuta a creare maschere indipendenti. La classe IntFlag rende questa tecnica più leggibile e sicura, soprattutto quando si gestiscono permessi o configurazioni composte.
Questa soluzione è particolarmente utile quando il numero di opzioni è noto e ogni opzione è indipendente dalle altre. Per dati più descrittivi o variabili, invece, può essere preferibile usare un dizionario, una classe o un insieme.
