Python: variabili e operatori bit a bit per gestire permessi e flag

by theArchitect
SHARE
Python: variabili e operatori bit a bit per gestire permessi e flag
© Guida-HTML5.it

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.

Approfondisci con risorse ufficiali

SHARE