9. Combinare condizioni con la logica booleana
Nel Capitolo 7, abbiamo imparato i valori booleani e le condizioni semplici usando gli operatori di confronto. Nel Capitolo 8, abbiamo usato queste condizioni per prendere decisioni con le istruzioni if. Ma i programmi del mondo reale spesso devono verificare più condizioni contemporaneamente. Dovremmo concedere l’accesso se l’utente ha la password corretta e ha effettuato l’accesso? Dovremmo mostrare un avviso se la temperatura è troppo calda o troppo fredda? Dovremmo procedere se il file non è vuoto?
Python fornisce tre operatori logici che ci permettono di combinare e modificare i valori booleani: and, or e not. Questi operatori sono i mattoni fondamentali per esprimere una logica decisionale complessa nei tuoi programmi.
9.1) Operatori logici and, or e not
I tre operatori logici lavorano con valori booleani (o valori che possono essere trattati come booleani) per produrre nuovi risultati booleani.
9.1.1) L’operatore and
L’operatore and restituisce True solo quando entrambi gli operandi sono veri. Se uno dei due operandi è falso, l’intera espressione è falsa.
# Entrambe le condizioni devono essere vere
age = 25
has_license = True
can_rent_car = age >= 21 and has_license
print(can_rent_car) # Output: True
# Se una delle condizioni è falsa, il risultato è False
age = 18
can_rent_car = age >= 21 and has_license
print(can_rent_car) # Output: FalsePensa a and come a un guardiano severo: tutte le condizioni devono essere soddisfatte affinché il controllo complessivo vada a buon fine.
Tabella di verità per and:
| Operando sinistro | Operando destro | Risultato |
|---|---|---|
True | True | True |
True | False | False |
False | True | False |
False | False | False |
9.1.2) L’operatore or
L’operatore or restituisce True quando almeno uno degli operandi è vero. Restituisce False solo quando entrambi gli operandi sono falsi.
# Almeno una condizione deve essere vera
is_weekend = True
is_holiday = False
can_sleep_in = is_weekend or is_holiday
print(can_sleep_in) # Output: True
# Entrambe le condizioni false
is_weekend = False
is_holiday = False
can_sleep_in = is_weekend or is_holiday
print(can_sleep_in) # Output: FalsePensa a or come a un guardiano indulgente: basta soddisfare una condizione per passare.
Tabella di verità per or:
| Operando sinistro | Operando destro | Risultato |
|---|---|---|
True | True | True |
True | False | True |
False | True | True |
False | False | False |
Ecco un esempio pratico per un sistema di idoneità a uno sconto:
# Il cliente ottiene lo sconto se è uno studente OPPURE un anziano
age = 68
is_student = False
gets_discount = is_student or age >= 65
print(f"Eligible for discount: {gets_discount}") # Output: Eligible for discount: True
# Un altro cliente
age = 30
is_student = False
gets_discount = is_student or age >= 65
print(f"Eligible for discount: {gets_discount}") # Output: Eligible for discount: FalseIl primo cliente è idoneo perché soddisfa uno dei criteri (anziano), anche se non è uno studente.
9.1.3) L’operatore not
L’operatore not è un operatore unario (lavora su un singolo operando) che inverte un valore booleano. Trasforma True in False e False in True.
is_raining = False
is_sunny = not is_raining
print(is_sunny) # Output: True
is_raining = True
is_sunny = not is_raining
print(is_sunny) # Output: FalseTabella di verità per not:
| Operando | Risultato |
|---|---|
True | False |
False | True |
L’operatore not è particolarmente utile quando vuoi verificare l’opposto di una condizione:
# Controlla se un file NON è vuoto
file_size = 0
is_empty = file_size == 0
is_not_empty = not is_empty
print(f"File has content: {is_not_empty}") # Output: File has content: False
# Controlla se l'utente NON ha effettuato l'accesso
is_logged_in = False
needs_login_prompt = not is_logged_in
print(f"Show login prompt: {needs_login_prompt}") # Output: Show login prompt: True9.1.4) Combinare più operatori logici
Puoi combinare più operatori logici in un’unica espressione per costruire condizioni più sofisticate:
# Negozio online: spedizione gratuita se l'ordine supera i $50 OPPURE il cliente è premium
# E gli articoli sono disponibili
order_total = 45.00
is_premium = True
in_stock = True
gets_free_shipping = (order_total >= 50 or is_premium) and in_stock
print(f"Free shipping: {gets_free_shipping}") # Output: Free shipping: TrueTracciamo questa valutazione:
order_total >= 50valutaFalse(45.00 non è >= 50)is_premiumèTrueFalse or TruevalutaTruein_stockèTrueTrue and TruevalutaTrue
Ecco un altro esempio con il controllo degli accessi:
# L'utente può accedere al pannello admin se è un admin
# E (si trova nella rete interna OPPURE usa una VPN)
is_admin = True
on_internal_network = False
using_vpn = True
can_access_admin = is_admin and (on_internal_network or using_vpn)
print(f"Can access admin panel: {can_access_admin}") # Output: Can access admin panel: TrueNota le parentesi attorno a (on_internal_network or using_vpn). Sono importanti perché controllano l’ordine di valutazione, proprio come le parentesi nelle espressioni aritmetiche.
9.2) Precedenza degli operatori nelle espressioni booleane (ordine Not, And, Or)
Quando combini più operatori logici senza parentesi, Python segue regole di precedenza specifiche per determinare l’ordine di valutazione. Comprendere queste regole ti aiuta a scrivere condizioni corrette ed evitare bug sottili.
9.2.1) La gerarchia di precedenza
Python valuta gli operatori logici in questo ordine (dalla precedenza più alta alla più bassa):
not(precedenza più alta)and(precedenza media)or(precedenza più bassa)
Questo significa che not viene valutato per primo, poi and e infine or.
# Senza parentesi, la precedenza determina l'ordine
result = True or False and False
print(result) # Output: True
# Come Python valuta questo:
# Step 1: False and False → False (and ha precedenza più alta di or)
# Step 2: True or False → TrueVediamolo passo dopo passo con un esempio più dettagliato:
is_weekend = False
is_holiday = True
has_work = True
# Espressione: not has_work or is_weekend and is_holiday
free_time = not has_work or is_weekend and is_holiday
# Ordine di valutazione:
# Step 1: not has_work → not True → False
# Step 2: is_weekend and is_holiday → False and True → False
# Step 3: False or False → False
print(f"Has free time: {free_time}") # Output: Has free time: False9.2.2) Usare le parentesi per chiarezza
Anche quando comprendi le regole di precedenza, usare le parentesi rende il codice più chiaro e previene errori. Le parentesi sovrascrivono la precedenza predefinita e rendono esplicite le tue intenzioni.
# Ambiguo senza parentesi
result = True or False and False
print(result) # Output: True
# Chiaro con le parentesi - cosa intendevamo davvero?
result = (True or False) and False
print(result) # Output: False
result = True or (False and False)
print(result) # Output: TrueQueste due espressioni producono risultati diversi! Le parentesi cambiano completamente il significato.
9.2.3) Operatori di confronto e operatori logici insieme
Gli operatori di confronto (come <, >, ==, !=) hanno precedenza più alta degli operatori logici. Questo significa che i confronti vengono valutati prima delle operazioni logiche.
age = 25
income = 50000
# Non servono parentesi attorno ai confronti
eligible = age >= 18 and income >= 30000
print(f"Eligible for loan: {eligible}") # Output: Eligible for loan: True
# Python lo valuta come:
# Step 1: age >= 18 → True
# Step 2: income >= 30000 → True
# Step 3: True and True → True9.3) Valutazione short-circuit
Python usa la valutazione short-circuit quando valuta espressioni booleane con and e or. Questo significa che Python smette di valutare non appena conosce il risultato finale, saltando potenzialmente la valutazione di operandi successivi. Questo comportamento è sia un’ottimizzazione delle prestazioni sia una tecnica di programmazione utile.
9.3.1) Come short-circuita and
Con l’operatore and, se l’operando sinistro è False, Python sa che l’intera espressione deve essere False (perché entrambi gli operandi devono essere veri affinché and restituisca True). Di conseguenza, Python non valuta affatto l’operando destro.
# Dimostrazione semplice
x = 5
result = x < 3 and x > 10
print(result) # Output: False
# Valutazione di Python:
# Step 1: x < 3 → 5 < 3 → False
# Step 2: Poiché il lato sinistro è False, non valutare x > 10
# Step 3: Restituisci FalseEcco un esempio pratico che mostra perché la valutazione short-circuit è importante:
# Verifica se un numero è divisibile - evitando la divisione per zero
numerator = 100
denominator = 0
# Questo è sicuro grazie alla valutazione short-circuit
# Se denominator è 0, la divisione non avviene mai
is_divisible = denominator != 0 and numerator % denominator == 0
print(f"Is divisible: {is_divisible}") # Output: Is divisible: False
# Senza valutazione short-circuit, questo causerebbe un errore:
# denominator = 0
# result = numerator % denominator # ZeroDivisionError!L’espressione denominator != 0 valuta False, quindi Python non valuta mai numerator % denominator, che causerebbe un errore di divisione per zero.
Vediamo un altro esempio con operazioni sulle stringhe:
# Controllare in modo sicuro le proprietà di una stringa
text = ""
# Controlla se text non è vuoto E il primo carattere è maiuscolo
# Sicuro perché se text è vuoto, non proviamo mai ad accedere a text[0]
has_uppercase_start = len(text) > 0 and text[0].isupper()
print(f"Starts with uppercase: {has_uppercase_start}") # Output: Starts with uppercase: False
# Se provassimo questo senza il controllo della lunghezza:
# text = ""
# result = text[0].isupper() # IndexError: string index out of range9.3.2) Come short-circuita or
Con l’operatore or, se l’operando sinistro è True, Python sa che l’intera espressione deve essere True (perché è sufficiente che almeno un operando sia vero). Di conseguenza, Python non valuta l’operando destro.
# Dimostrazione semplice
x = 15
result = x > 10 or x < 5
print(result) # Output: True
# Valutazione di Python:
# Step 1: x > 10 → 15 > 10 → True
# Step 2: Poiché il lato sinistro è True, non valutare x < 5
# Step 3: Restituisci True9.3.3) Applicazioni pratiche della valutazione short-circuit
Evitare errori:
# Accedere in modo sicuro agli elementi di una lista
numbers = [1, 2, 3]
index = 5
# Controlla se index è valido prima di accedere
is_valid = index < len(numbers) and numbers[index] > 0
print(f"Valid and positive: {is_valid}") # Output: Valid and positive: False
# Senza short-circuit, questo andrebbe in crash:
# is_valid = numbers[index] > 0 # IndexError!Verificare più condizioni in modo efficiente:
# Validazione di un form - fermarsi al primo errore
email = "user@example.com"
password = "pass"
age = 25
# Verifica ogni requisito in ordine di probabilità di fallimento
valid_form = (
len(email) > 0 and # Controllo rapido
"@" in email and # Controllo rapido
len(password) >= 8 and # Controllo rapido
age >= 18 # Controllo rapido
)
print(f"Form valid: {valid_form}") # Output: Form valid: False
# Si ferma al controllo sulla lunghezza della password, non valuta age9.4) Cosa restituiscono gli operatori and e or con operandi non booleani e comuni insidie nelle espressioni booleane
Finora, abbiamo visto and, or e not funzionare con valori booleani. Ma gli operatori logici di Python hanno un comportamento interessante: possono lavorare con qualsiasi valore, non solo con True e False. Comprendere questo comportamento ti aiuta a scrivere codice più conciso ed evitare errori comuni.
9.4.1) Comprendere truthiness e falsiness (ripasso)
Come abbiamo imparato nel Capitolo 7, Python tratta molti valori non booleani come “truthy” o “falsy” nei contesti booleani:
Valori falsy (trattati come False):
FalseNone0(zero di qualsiasi tipo numerico)""(stringa vuota)[](lista vuota){}(dizionario vuoto)()(tupla vuota)
Valori truthy (trattati come True):
True- Qualsiasi numero diverso da zero
- Qualsiasi stringa non vuota
- Qualsiasi collezione non vuota
# Dimostrare la truthiness
if "hello":
print("Le stringhe non vuote sono truthy") # Output: Non-empty strings are truthy
if 0:
print("Questo non verrà stampato") # Zero è falsy
else:
print("Zero è falsy") # Output: Zero is falsy
if [1, 2, 3]:
print("Le liste non vuote sono truthy") # Output: Non-empty lists are truthy9.4.2) Cosa restituisce davvero and
L’operatore and non restituisce sempre True o False. Invece, restituisce uno dei suoi operandi:
- Se l’operando sinistro è falsy,
andrestituisce l’operando sinistro (senza valutare il destro) - Se l’operando sinistro è truthy,
andrestituisce l’operando destro
# and restituisce il primo valore falsy, oppure l'ultimo valore se tutti sono truthy
result = 5 and 10
print(result) # Output: 10
result = 0 and 10
print(result) # Output: 0
result = "hello" and "world"
print(result) # Output: world
result = "" and "world"
print(result) # Output: (empty string)
result = None and "world"
print(result) # Output: NoneTracciamo questi esempi passo dopo passo:
# Esempio 1: Entrambi truthy
result = 5 and 10
# Step 1: 5 è truthy, quindi valuta il lato destro
# Step 2: Restituisci il valore del lato destro: 10
print(result) # Output: 10
# Esempio 2: Il sinistro è falsy
result = 0 and 10
# Step 1: 0 è falsy, quindi restituiscilo immediatamente
# Step 2: Non valutare il lato destro
print(result) # Output: 0
# Esempio 3: Entrambe stringhe truthy
result = "hello" and "world"
# Step 1: "hello" è truthy, quindi valuta il lato destro
# Step 2: Restituisci il valore del lato destro: "world"
print(result) # Output: world9.4.3) Cosa restituisce davvero or
Allo stesso modo, l’operatore or restituisce uno dei suoi operandi:
- Se l’operando sinistro è truthy,
orrestituisce l’operando sinistro (senza valutare il destro) - Se l’operando sinistro è falsy,
orrestituisce l’operando destro
# or restituisce il primo valore truthy, oppure l'ultimo valore se tutti sono falsy
result = 5 or 10
print(result) # Output: 5
result = 0 or 10
print(result) # Output: 10
result = "" or "default"
print(result) # Output: default
result = "hello" or "world"
print(result) # Output: hello
result = None or 0
print(result) # Output: 0Tracciamo questi esempi:
# Esempio 1: Il sinistro è truthy
result = 5 or 10
# Step 1: 5 è truthy, quindi restituiscilo immediatamente
# Step 2: Non valutare il lato destro
print(result) # Output: 5
# Esempio 2: Il sinistro è falsy
result = 0 or 10
# Step 1: 0 è falsy, quindi valuta il lato destro
# Step 2: Restituisci il valore del lato destro: 10
print(result) # Output: 10
# Esempio 3: Entrambi falsy
result = None or 0
# Step 1: None è falsy, quindi valuta il lato destro
# Step 2: Restituisci il valore del lato destro: 0 (anche se è anch'esso falsy)
print(result) # Output: 09.4.4) Usi pratici di or per valori predefiniti
Un pattern comune è usare or per fornire valori predefiniti:
# Preferenze utente con valori predefiniti
user_theme = "" # L'utente non ha impostato un tema
theme = user_theme or "light"
print(f"Theme: {theme}") # Output: Theme: light
user_theme = "dark"
theme = user_theme or "light"
print(f"Theme: {theme}") # Output: Theme: dark
# Valori di configurazione
max_retries = None # Non configurato
retries = max_retries or 3
print(f"Retries: {retries}") # Output: Retries: 3
max_retries = 5
retries = max_retries or 3
print(f"Retries: {retries}") # Output: Retries: 5Questo pattern funziona perché se il lato sinistro è falsy (stringa vuota, None, 0, ecc.), or restituisce il lato destro (il valore predefinito).
9.4.12) Riepilogo di cosa restituiscono gli operatori
Ecco un riepilogo completo di cosa restituisce ciascun operatore logico:
Operatore and:
- Restituisce il primo operando falsy
- Se tutti gli operandi sono truthy, restituisce l’ultimo operando
- Usa la valutazione short-circuit (si ferma al primo valore falsy)
Operatore or:
- Restituisce il primo operando truthy
- Se tutti gli operandi sono falsy, restituisce l’ultimo operando
- Usa la valutazione short-circuit (si ferma al primo valore truthy)
Operatore not:
- Restituisce sempre un booleano (
TrueoFalse) notconverte l’operando in un booleano, poi lo nega
# Dimostrare tutti e tre gli operatori
print(5 and 10) # Output: 10 (both truthy, return last)
print(0 and 10) # Output: 0 (first falsy, return it)
print(5 or 10) # Output: 5 (first truthy, return it)
print(0 or 10) # Output: 10 (first falsy, evaluate second)
print(not 5) # Output: False (5 is truthy, not returns boolean)
print(not 0) # Output: True (0 is falsy, not returns boolean)
print(not "") # Output: True (empty string is falsy)
print(not "hello") # Output: False (non-empty string is truthy)Comprendere questi comportamenti ti aiuta a scrivere codice più conciso e più “Pythonic”, ma dai sempre priorità alla chiarezza. Se usare queste funzionalità rende il tuo codice più difficile da capire, è meglio essere espliciti.
In questo capitolo, abbiamo esplorato come combinare condizioni semplici in logica booleana complessa usando gli operatori and, or e not di Python. Abbiamo imparato la precedenza degli operatori, la valutazione short-circuit e il comportamento sorprendente degli operatori logici con valori non booleani. Abbiamo anche esaminato insidie comuni e best practice per scrivere espressioni booleane chiare e corrette.
Questi strumenti ti permettono di esprimere una logica decisionale sofisticata nei tuoi programmi. In combinazione con le istruzioni if del Capitolo 8, ora puoi gestire praticamente qualsiasi logica condizionale richiesta dai tuoi programmi. Nel prossimo capitolo, esploreremo le espressioni condizionali, che forniscono un modo compatto per scegliere tra due valori in base a una condizione.