Python & AI Tutorials Logo
Programmazione Python

21. Ambito delle variabili e risoluzione dei nomi

Quando crei una variabile in Python, dove “vive”? Una funzione può vedere variabili create al di fuori di essa? Il codice al di fuori di una funzione può accedere a variabili create al suo interno? Queste domande riguardano lo scope (ambito) - la regione del tuo programma in cui un nome è visibile e può essere usato.

Comprendere lo scope è cruciale per scrivere funzioni che funzionino correttamente e in modo prevedibile. Senza questa conoscenza, potresti creare accidentalmente bug in cui le variabili non hanno i valori che ti aspetti, o in cui le modifiche alle variabili non persistono come previsto.

In questo capitolo esploreremo come Python determina a quale variabile si riferisce un nome, come controllare dove le variabili sono accessibili e cosa succede quando elimini un nome. Alla fine, capirai le regole che governano la visibilità delle variabili nei programmi Python.

21.1) Variabili locali e globali

Ogni variabile in Python esiste all’interno di uno specifico scope (ambito) - una regione di codice in cui quel nome di variabile è definito e accessibile. I due ambiti più fondamentali sono locale e globale.

Comprendere l’ambito globale

Le variabili create al livello superiore del tuo programma - al di fuori di qualsiasi funzione - esistono nell’ambito globale. Queste sono chiamate variabili globali, e sono accessibili da qualsiasi punto del tuo modulo dopo che sono state definite.

python
# Variabile globale - definita a livello di modulo
total_users = 0
 
def show_user_count():
    # Questa funzione può LEGGERE la variabile globale
    print(f"Total users: {total_users}")
 
show_user_count()  # Output: Total users: 0
print(total_users)  # Output: 0

In questo esempio, total_users è una variabile globale. Sia la funzione show_user_count() sia il codice a livello di modulo possono accedervi. Pensa alle variabili globali come visibili in tutto il file del tuo programma.

Comprendere l’ambito locale

Le variabili create all’interno di una funzione esistono nell’ambito locale di quella funzione. Queste sono chiamate variabili locali, e sono accessibili solo all’interno della funzione in cui sono definite. Una volta che la funzione termina l’esecuzione, le variabili locali scompaiono.

python
def calculate_discount(price):
    # discount_rate è LOCALE a questa funzione
    discount_rate = 0.15
    discount_amount = price * discount_rate
    return discount_amount
 
result = calculate_discount(100)
print(result)  # Output: 15.0
 
# Questo causerebbe un errore - discount_rate qui non esiste
# print(discount_rate)  # NameError: name 'discount_rate' is not defined

Le variabili discount_rate e discount_amount esistono solo mentre calculate_discount() è in esecuzione. Dopo che la funzione ritorna, questi nomi non esistono più. In realtà è una buona cosa: impedisce alle funzioni di “sporcare” il programma con variabili temporanee.

Perché l’ambito locale è importante

L’ambito locale fornisce incapsulamento (encapsulation) - ogni funzione ha il proprio spazio di lavoro privato. Questo significa che puoi usare gli stessi nomi di variabile in funzioni diverse senza conflitti:

python
def calculate_tax(amount):
    rate = 0.08  # Variabile locale
    return amount * rate
 
def calculate_shipping(weight):
    rate = 5.00  # Variabile locale diversa con lo stesso nome
    return weight * rate
 
tax = calculate_tax(100)
shipping = calculate_shipping(3)
 
print(f"Tax: ${tax}")         # Output: Tax: $8.0
print(f"Shipping: ${shipping}")  # Output: Shipping: $15.0

Entrambe le funzioni usano una variabile chiamata rate, ma sono variabili completamente separate in ambiti locali diversi. Le modifiche a rate in una funzione non influenzano rate nell’altra funzione. Questo isolamento rende le funzioni più affidabili e più facili da capire.

Leggere variabili globali dalle funzioni

Le funzioni possono leggere le variabili globali senza alcuna sintassi speciale:

python
# Configurazione globale
max_login_attempts = 3
 
def check_login(password):
    # Lettura della variabile globale
    if password == "secret123":
        return "Login successful"
    else:
        return f"Invalid password. You have {max_login_attempts} attempts."
 
result = check_login("wrong")
print(result)  # Output: Invalid password. You have 3 attempts.

La funzione check_login() può leggere max_login_attempts perché è una variabile globale. Tuttavia, c’è una limitazione importante che dobbiamo capire.

La regola: l’assegnazione crea variabili locali

È qui che lo scope diventa complicato. Se assegni a un nome di variabile dentro una funzione, Python crea una nuova variabile locale con quel nome, anche se esiste una variabile globale con lo stesso nome:

python
counter = 0  # Variabile globale
 
def increment_counter():
    # ATTENZIONE: questo crea una NUOVA variabile locale chiamata counter - solo a scopo dimostrativo
    # PROBLEMA: tentativo di leggere counter prima di assegnarle un valore in locale
    counter = counter + 1  # UnboundLocalError: local variable 'counter' referenced before assignment
    print(counter)
 
# increment_counter() # Questa chiamata produce UnboundLocalError

Questo codice fallisce perché Python vede l’assegnazione counter = counter + 1 e decide che counter deve essere una variabile locale. Ma poi, quando prova a valutare counter + 1, la variabile locale counter non ha ancora un valore: stiamo cercando di usarla prima di averle assegnato un valore.

Questa è una fonte comune di confusione. La regola è: se una funzione assegna a un nome di variabile in qualsiasi punto del suo corpo, quel nome viene trattato come locale per l’intera funzione, anche prima dell’assegnazione.

Vediamolo più chiaramente:

python
message = "Hello"  # Variabile globale
 
def show_message():
    print(message)  # Questo funziona - sta solo leggendo la globale
    
def change_message():
    # ATTENZIONE: questo dimostra un errore comune - solo a scopo dimostrativo
    # PROBLEMA: Python vede l'assegnazione sotto, quindi message viene trattata come locale ovunque
    print(message)  # UnboundLocalError!
    message = "Goodbye"  # Questo rende message locale per l'INTERA funzione
 
show_message()  # Output: Hello
# change_message()  # Questa chiamata produce UnboundLocalError

La funzione show_message() funziona bene perché si limita a leggere message. Ma change_message() fallisce perché l’assegnazione alla seconda riga fa sì che Python tratti message come locale per l’intera funzione, incluso lo statement print() che viene prima dell’assegnazione.

I parametri sono variabili locali

I parametri di funzione sono variabili locali che ricevono i loro valori iniziali dagli argomenti passati quando la funzione viene chiamata:

python
def greet(name):  # 'name' è una variabile locale
    greeting = f"Hello, {name}!"  # anche 'greeting' è locale
    return greeting
 
message = greet("Alice")
print(message)  # Output: Hello, Alice!
 
# Né 'name' né 'greeting' esistono qui
# print(name)  # NameError

Il parametro name esiste solo all’interno della funzione greet(). Viene creato quando la funzione viene chiamata e scompare quando la funzione ritorna.

Esempio pratico: calcolo del totale del carrello

Vediamo come ambito locale e globale lavorano insieme in uno scenario realistico:

python
# Configurazione globale
tax_rate = 0.08
free_shipping_threshold = 50
 
def calculate_total(subtotal):
    # Variabili locali per questo calcolo
    tax = subtotal * tax_rate  # Lettura della globale tax_rate
    
    # Determinare il costo di spedizione
    if subtotal >= free_shipping_threshold:  # Lettura della soglia globale
        shipping = 0
    else:
        shipping = 5.99
    
    total = subtotal + tax + shipping
    return total
 
# Calcolo per diversi valori del carrello
cart1 = calculate_total(30)
cart2 = calculate_total(60)
 
print(f"Cart 1 total: ${cart1:.2f}")  # Output: Cart 1 total: $38.39
print(f"Cart 2 total: ${cart2:.2f}")  # Output: Cart 2 total: $64.80

In questo esempio:

  • tax_rate e free_shipping_threshold sono valori di configurazione globali
  • subtotal, tax, shipping e total sono locali a ogni chiamata di calculate_total()
  • Ogni chiamata della funzione ottiene il proprio insieme separato di variabili locali
  • La funzione può leggere la configurazione globale ma non la modifica

Questa separazione delle responsabilità rende il codice chiaro: le variabili globali contengono configurazione che vale ovunque, mentre le variabili locali contengono risultati di calcolo temporanei specifici di ciascuna chiamata della funzione.

21.2) La regola LEGB per la risoluzione dei nomi

Quando Python incontra un nome di variabile, come fa a sapere a quale variabile ti stai riferendo? Python segue un ordine di ricerca specifico chiamato regola LEGB. LEGB sta per Local, Enclosing, Global, Built-in - i quattro ambiti che Python ricerca, in quest’ordine.

I quattro ambiti in LEGB

Comprendiamo ciascun ambito nella gerarchia LEGB:

  1. Local (L): l’ambito della funzione corrente
  2. Enclosing (E): l’ambito di eventuali funzioni contenitrici (funzioni che contengono la funzione corrente)
  3. Global (G): l’ambito a livello di modulo
  4. Built-in (B): i nomi built-in di Python come print, len, int, ecc.

Quando usi un nome di variabile, Python cerca questi ambiti in ordine: L → E → G → B. Usa la prima corrispondenza che trova e smette di cercare.

Ambito locale: il primo posto in cui Python cerca

Python controlla sempre prima l’ambito locale:

python
def calculate_price():
    price = 100  # Variabile locale
    tax = 0.08   # Variabile locale
    total = price * (1 + tax)
    return total
 
result = calculate_price()
print(result)  # Output: 108.0

Quando Python vede price, tax e total dentro calculate_price(), li trova nell’ambito locale e usa quei valori. La ricerca si ferma all’ambito locale: Python non ha bisogno di cercare oltre.

Ambito globale: quando il locale non ce l’ha

Se un nome non viene trovato localmente, Python controlla l’ambito globale:

python
# Variabili globali
default_tax_rate = 0.08
default_currency = "USD"
 
def calculate_price(amount):
    # 'amount' è locale, trovato immediatamente
    # 'default_tax_rate' non è locale, trovato nell'ambito globale
    total = amount * (1 + default_tax_rate)
    return total
 
result = calculate_price(100)
print(result)  # Output: 108.0

Quando Python incontra default_tax_rate all’interno della funzione, non lo trova localmente, quindi cerca nell’ambito globale e lo trova lì.

Ambito built-in: i nomi predefiniti di Python

Se un nome non viene trovato nell’ambito locale o globale, Python controlla l’ambito built-in: i nomi che Python fornisce automaticamente:

python
def process_data(numbers):
    # 'numbers' è locale
    # 'len' non è locale né globale - è built-in
    count = len(numbers)
    
    # anche 'max' è built-in
    maximum = max(numbers)
    
    return count, maximum
 
data = [10, 25, 15, 30, 20]
result = process_data(data)
print(result)  # Output: (5, 30)

I nomi len e max non sono definiti nel tuo codice: sono funzioni built-in fornite da Python. Quando Python non trova questi nomi localmente o globalmente, controlla l’ambito built-in e li trova lì.

Ambito enclosing: funzioni annidate

L’ambito enclosing entra in gioco quando hai funzioni annidate: funzioni definite dentro altre funzioni. È qui che la “E” in LEGB diventa importante:

python
def outer_function():
    outer_var = "I'm from outer"  # Nell'ambito enclosing per inner_function
    
    def inner_function():
        inner_var = "I'm from inner"  # Locale a inner_function
        # inner_function può vedere sia inner_var (locale) sia outer_var (enclosing)
        print(inner_var)   # Output: I'm from inner
        print(outer_var)   # Output: I'm from outer
    
    inner_function()
 
outer_function()

Per inner_function(), l’ambito di outer_function() è un ambito enclosing. Quando inner_function() fa riferimento a outer_var, Python cerca:

  1. Ambito locale di inner_function() - non trovato
  2. Ambito enclosing di outer_function() - trovato! Usa questo valore

LEGB in azione: esempio semplice

Vediamo tutti e quattro gli ambiti lavorare insieme in un esempio chiaro e diretto:

python
# Built-in: len (Python lo fornisce)
# Globale: multiplier
multiplier = 10
 
def outer(x):
    # Ambito enclosing per inner
    y = 5
    
    def inner(z):
        # Ambito locale
        # z è locale (L)
        # y viene dall'ambito enclosing (E)
        # multiplier viene dall'ambito globale (G)
        # len viene dall'ambito built-in (B)
        result = len([z, y, multiplier])  # Usa tutti e quattro gli ambiti!
        return z + y + multiplier
 
    return inner(3)
 
answer = outer(100)
print(answer)  # Output: 18

Quando Python valuta z + y + multiplier dentro inner():

  1. L (Local): trova z = 3
  2. E (Enclosing): trova y = 5 in outer()
  3. G (Global): trova multiplier = 10
  4. B (Built-in): trova la funzione len

Questo esempio dimostra chiaramente come Python cerchi in tutti e quattro gli ambiti per risolvere i nomi.

Shadowing: quando gli ambiti interni nascondono i nomi esterni

Se lo stesso nome esiste in più ambiti, l’ambito più interno “vince”: questo si chiama shadowing:

python
value = "global"
 
def outer():
    value = "enclosing"
    
    def inner():
        value = "local"
        print(value)  # Which value?
    
    inner()
    print(value)  # Which value?
 
outer()
print(value)  # Which value?

Output:

local
enclosing
global

Ogni statement print() vede un value diverso perché Python si ferma alla prima corrispondenza:

  • Dentro inner(): trova value localmente → stampa "local"
  • Dentro outer() ma fuori da inner(): trova value nell’ambito di outer() → stampa "enclosing"
  • A livello di modulo: trova value globalmente → stampa "global"

Visualizzare l’ordine di ricerca LEGB

No

No

No

No

Riferimento al nome

Trovato nel locale?

Usa il valore locale

Trovato nell'enclosing?

Usa il valore dell'enclosing

Trovato nel globale?

Usa il valore globale

Trovato nel built-in?

Usa il valore built-in

NameError

Questo diagramma mostra il processo di ricerca di Python. Inizia dall’ambito più interno e procede verso l’esterno. Se il nome non viene trovato in nessun ambito, Python solleva un NameError.

Perché LEGB è importante per scrivere funzioni

Capire LEGB ti aiuta a:

  1. Prevedere i valori delle variabili: sai esattamente quale variabile userà Python
  2. Evitare conflitti di nomi: capisci quando i nomi si ombreggiano a vicenda
  3. Progettare funzioni migliori: puoi decidere quale ambito è appropriato per ogni variabile
  4. Fare debugging di problemi di ambito: quando le variabili non hanno i valori attesi, puoi ripercorrere LEGB

La regola LEGB è fondamentale per come Python risolve i nomi. Ogni volta che usi una variabile, Python sta seguendo questa regola dietro le quinte.

21.3) Usare con cautela la parola chiave global

Abbiamo visto che le funzioni possono leggere le variabili globali, ma cosa succede se devi modificare una variabile globale dall’interno di una funzione? È qui che entra in gioco la parola chiave global - ma dovrebbe essere usata con parsimonia e con attenzione.

Il problema: l’assegnazione crea variabili locali

Come abbiamo imparato prima, assegnare a una variabile dentro una funzione crea una variabile locale:

python
counter = 0  # Variabile globale
 
def increment():
    # ATTENZIONE: questo crea una NUOVA variabile locale chiamata counter - solo a scopo dimostrativo
    # PROBLEMA: tentativo di leggere counter prima di assegnarle un valore in locale
    counter = counter + 1  # UnboundLocalError!
    
# increment()  # Questa chiamata produce UnboundLocalError

Questo fallisce perché Python vede l’assegnazione e tratta counter come locale per l’intera funzione. Ma stiamo cercando di leggere counter prima di averle assegnato un valore in locale.

Questo è uno degli errori più comuni quando si lavora con variabili globali. Il messaggio di errore UnboundLocalError: local variable 'counter' referenced before assignment ti dice esattamente cosa è successo: Python ha deciso che counter era locale (a causa dell’assegnazione), ma hai provato a usarla prima di darle un valore.

La soluzione: dichiarare le variabili come globali

La parola chiave global dice a Python: “Non creare una nuova variabile locale con questo nome. Usa invece la variabile globale.”

python
counter = 0  # Variabile globale
 
def increment():
    global counter  # Dì a Python di usare la counter globale
    counter = counter + 1  # Ora questo modifica la variabile globale
 
print(f"Before: {counter}")  # Output: Before: 0
increment()
print(f"After: {counter}")   # Output: After: 1
increment()
print(f"After again: {counter}")  # Output: After again: 2

La dichiarazione global counter deve venire prima di usare la variabile. Dice a Python che qualsiasi assegnazione a counter in questa funzione dovrebbe modificare la variabile globale, non crearne una locale.

Più variabili globali

Puoi dichiarare più variabili come globali in un’unica istruzione:

python
total_sales = 0
total_customers = 0
 
def record_sale(amount):
    global total_sales, total_customers
    total_sales += amount
    total_customers += 1
 
print(f"Sales: ${total_sales}, Customers: {total_customers}")
# Output: Sales: $0, Customers: 0
 
record_sale(25.50)
record_sale(30.00)
 
print(f"Sales: ${total_sales}, Customers: {total_customers}")
# Output: Sales: $55.5, Customers: 2

Sia total_sales sia total_customers sono dichiarate globali, quindi la funzione può modificare entrambe.

Quando usare global: stato condiviso

La parola chiave global è appropriata quando devi mantenere stato condiviso (shared state) - dati a cui più funzioni devono accedere e che devono modificare:

python
# Stato del gioco
player_score = 0
player_lives = 3
game_over = False
 
def award_points(points):
    global player_score
    player_score += points
    print(f"Score: {player_score}")
 
def lose_life():
    global player_lives, game_over
    player_lives -= 1
    print(f"Lives remaining: {player_lives}")
    
    if player_lives <= 0:
        game_over = True
        print("Game Over!")
 
def check_game_status():
    # Solo lettura delle globali - non serve la parola chiave global
    if game_over:
        return "Game Over"
    else:
        return f"Playing - Score: {player_score}, Lives: {player_lives}"
 
# Gioca la partita
award_points(100)    # Output: Score: 100
award_points(50)     # Output: Score: 150
lose_life()          # Output: Lives remaining: 2
print(check_game_status())  # Output: Playing - Score: 150, Lives: 2

Questo esempio mostra un uso appropriato di global: più funzioni devono modificare uno stato di gioco condiviso. Tuttavia, nota che check_game_status() non ha bisogno di global perché si limita a leggere le variabili.

Perché global dovrebbe essere usata con cautela

Sebbene global a volte sia necessaria, abusarne può rendere il codice più difficile da capire e da mantenere. Ecco perché:

Problema 1: dipendenze nascoste

Quando le funzioni modificano variabili globali, non è ovvio dalla chiamata della funzione cosa stia cambiando:

python
total = 0
 
def add_to_total(value):
    global total
    total += value
 
# Cosa fa questa funzione? Non puoi saperlo senza leggere il suo codice
add_to_total(10)

Confrontalo con una funzione che ritorna un valore:

python
def add_to_total(current_total, value):
    return current_total + value
 
total = 0
total = add_to_total(total, 10)  # Chiaro: total viene aggiornato

La seconda versione rende esplicito che total viene modificato.

Problema 2: il testing diventa più difficile

Le funzioni che modificano lo stato globale sono più difficili da testare perché devi predisporre e reimpostare le variabili globali:

python
# Difficile da testare - dipende dallo stato globale
score = 0
 
def add_score(points):
    global score
    score += points
 
# Ogni test deve reimpostare score
# Test 1
score = 0
add_score(10)
assert score == 10
 
# Test 2 - bisogna reimpostare score di nuovo
score = 0
add_score(20)
assert score == 20

Problema 3: le funzioni non sono riutilizzabili

Le funzioni che dipendono da specifiche variabili globali non possono essere riutilizzate facilmente in altri programmi:

python
# Questa funzione funziona solo se c'è una variabile globale chiamata 'inventory'
inventory = []
 
def add_item(item):
    global inventory
    inventory.append(item)

Alternative migliori a global

In molti casi, puoi evitare global usando valori di ritorno e parametri:

Invece di modificare lo stato globale:

python
# Usare global (meno ideale)
balance = 1000
 
def withdraw(amount):
    global balance
    if amount <= balance:
        balance -= amount
        return True
    return False
 
withdraw(100)
print(balance)  # Output: 900

Usa valori di ritorno:

python
# Usare valori di ritorno (meglio)
def withdraw(balance, amount):
    if amount <= balance:
        return balance - amount, True
    return balance, False
 
balance = 1000
balance, success = withdraw(balance, 100)
print(balance)  # Output: 900

La seconda versione è più flessibile, testabile e riutilizzabile.

Quando global è davvero appropriata

Esistono usi legittimi di global:

  1. Configurazione che deve davvero essere globale:
python
# Impostazioni a livello di applicazione
debug_mode = False
log_level = "INFO"
 
def enable_debug():
    global debug_mode, log_level
    debug_mode = True
    log_level = "DEBUG"
  1. Contatori per debugging o statistiche:
python
# Traccia le chiamate di funzione per il debugging
_function_call_count = 0
 
def tracked_function():
    global _function_call_count
    _function_call_count += 1
    # ... resto della funzione

Punti chiave su global

  • Usa global solo quando hai davvero bisogno di modificare lo stato a livello di modulo
  • Preferisci invece ritornare valori e usare parametri
  • Quando usi global, documenta perché è necessario
  • Valuta se il tuo design potrebbe essere migliorato per evitare global
  • Ricorda: leggere variabili globali non richiede la parola chiave global - solo modificarle lo richiede

21.4) Usare nonlocal per modificare variabili nelle funzioni contenitrici

Quando hai funzioni annidate, potresti aver bisogno di modificare una variabile dall’ambito di una funzione contenitrice. La parola chiave nonlocal serve a questo scopo: è come global, ma per gli ambiti delle funzioni contenitrici invece che per l’ambito globale.

Il problema: modificare variabili enclosing

Proprio come l’assegnazione crea variabili locali per impostazione predefinita, lo stesso problema si verifica con gli ambiti enclosing:

python
def outer():
    count = 0  # Variabile nell'ambito di outer
    
    def inner():
        # ATTENZIONE: questo crea una NUOVA variabile locale chiamata count - solo a scopo dimostrativo
        # PROBLEMA: tentativo di leggere count prima di assegnarle un valore in locale
        count = count + 1  # UnboundLocalError!
        print(count)
    
    inner()
 
# outer()  # Questa chiamata produce UnboundLocalError

Python vede l’assegnazione a count in inner() e la tratta come una variabile locale. Ma stiamo cercando di leggerla prima di assegnarle un valore in locale, causando un errore.

La soluzione: la keyword nonlocal

La parola chiave nonlocal dice a Python: “Questa variabile non è locale: cercala nell’ambito della funzione contenitrice e usa quella.”

python
def outer():
    count = 0  # Variabile nell'ambito di outer
    
    def inner():
        nonlocal count  # Usa la count dall'ambito di outer
        count = count + 1
        print(f"Count in inner: {count}")
    
    print(f"Count before: {count}")  # Output: Count before: 0
    inner()                          # Output: Count in inner: 1
    print(f"Count after: {count}")   # Output: Count after: 1
 
outer()

Ora inner() può modificare la variabile count dall’ambito di outer(). La modifica persiste dopo il ritorno di inner() perché stiamo modificando la variabile effettiva nell’ambito enclosing.

Perché nonlocal è utile: funzioni che ricordano lo stato

La parola chiave nonlocal abilita un pattern potente in cui le funzioni interne possono mantenere e modificare stato dal loro ambito enclosing. Impareremo in dettaglio le closure e le factory function nel Capitolo 23, ma per ora capisci che nonlocal permette alle funzioni interne di modificare variabili dagli ambiti enclosing.

Ecco un esempio semplice che mostra come funziona nonlocal:

python
def create_counter():
    count = 0  # Questa variabile è nell'ambito enclosing per increment
    
    def increment():
        nonlocal count  # Modifica la count dall'ambito enclosing
        count += 1
        return count
    
    return increment  # Ritorna la funzione interna
 
# Crea un contatore
counter1 = create_counter()
 
print(counter1())  # Output: 1
print(counter1())  # Output: 2
print(counter1())  # Output: 3
 
# Crea un altro contatore indipendente
counter2 = create_counter()
 
print(counter2())  # Output: 1
print(counter2())  # Output: 2

Ogni chiamata a create_counter() crea una nuova variabile count e una nuova funzione increment() che può modificare quella specifica count usando nonlocal.

nonlocal vs global

È importante capire la differenza:

python
x = "global"
 
def outer():
    x = "enclosing"
    
    def use_global():
        global x  # Si riferisce alla x globale
        print(f"use_global sees: {x}")  # Output: use_global sees: global
    
    def use_nonlocal():
        nonlocal x  # Si riferisce alla x di outer
        print(f"use_nonlocal sees: {x}")  # Output: use_nonlocal sees: enclosing
    
    use_global()
    use_nonlocal()
 
outer()
  • global si riferisce sempre all’ambito a livello di modulo
  • nonlocal si riferisce all’ambito della funzione contenitrice più vicina

Quando non puoi usare nonlocal

La parola chiave nonlocal funziona solo con gli ambiti enclosing delle funzioni. Non puoi usarla per:

  1. Ambito globale (usa global invece):
python
x = "global"
 
def func():
    nonlocal x  # SyntaxError: no binding for nonlocal 'x' found
    x = "modified"
  1. Variabili che non esistono in nessun ambito enclosing:
python
def outer():
    def inner():
        nonlocal count  # SyntaxError: no binding for nonlocal 'count' found

Punti chiave su nonlocal

  • Usa nonlocal per modificare variabili dagli ambiti enclosing delle funzioni
  • nonlocal cerca negli ambiti enclosing delle funzioni, non nell’ambito globale
  • Leggere variabili enclosing non richiede nonlocal - solo modificarle lo richiede
  • nonlocal abilita pattern potenti per creare funzioni con stato privato
  • Impareremo di più su closure e factory function nel Capitolo 23

La parola chiave nonlocal è particolarmente utile per creare funzioni che mantengono uno stato privato, come abbiamo visto con l’esempio del contatore.

21.5) Eliminare nomi (non oggetti) con del e cosa significa

A volte devi rimuovere una variabile dal namespace del tuo programma: magari per liberare memoria in programmi a lunga esecuzione, ripulire variabili temporanee o rimuovere voci da collezioni. L’istruzione del di Python gestisce questi compiti, ma è importante capire esattamente cosa fa e cosa non fa.

L’istruzione del in Python è spesso fraintesa. Non elimina gli oggetti: elimina i nomi (binding delle variabili). Comprendere questa distinzione è cruciale per capire come Python gestisce memoria e riferimenti.

Cosa fa davvero del

L’istruzione del rimuove un nome dall’ambito corrente:

python
x = 42
print(x)  # Output: 42
 
del x
 
# print(x)  # NameError: name 'x' is not defined

Dopo del x, il nome x non esiste più nell’ambito corrente. Se provi a usarlo, Python solleva un NameError perché il nome non è più definito.

Eliminare nomi vs eliminare oggetti

Questo è il punto chiave: del rimuove il nome, non necessariamente l’oggetto a cui il nome si riferisce:

python
# Crea una lista e due nomi che si riferiscono ad essa
original = [1, 2, 3]
reference = original  # Entrambi i nomi si riferiscono alla stessa lista
 
print(original)   # Output: [1, 2, 3]
print(reference)  # Output: [1, 2, 3]
 
# Elimina un nome
del original
 
# La lista esiste ancora perché 'reference' si riferisce ancora ad essa
print(reference)  # Output: [1, 2, 3]
 
# print(original)  # NameError: name 'original' is not defined

La lista [1, 2, 3] continua a esistere perché reference si riferisce ancora ad essa. Eliminare original ha rimosso solo quel nome: non ha eliminato l’oggetto lista stesso.

Quando gli oggetti vengono davvero eliminati

Python elimina automaticamente gli oggetti quando non sono più referenziati da alcun nome. Questo si chiama garbage collection:

python
data = [1, 2, 3]  # La lista viene creata, 'data' si riferisce ad essa
 
del data  # Il nome 'data' viene eliminato
 
# Ora la lista non ha riferimenti, quindi Python alla fine la eliminerà
# (Questo avviene automaticamente - non devi fare nulla)

Quando eliminiamo data, la lista [1, 2, 3] non ha riferimenti rimanenti, quindi il garbage collector di Python alla fine recupererà la memoria. Ma questo avviene automaticamente: non controlli quando.

Eliminare elementi dalle collezioni

L’istruzione del può anche rimuovere elementi dalle collezioni, ma questo è fondamentalmente diverso dall’eliminare nomi. Quando usi del con indicizzazione o slicing di una collezione, stai modificando la collezione stessa, non eliminando un nome.

Questa è una distinzione importante: quando scrivi del numbers[2], stai chiamando un metodo speciale sull’oggetto lista per rimuovere un elemento. Il nome numbers esiste ancora e si riferisce ancora allo stesso oggetto lista: la lista ha solo meno elementi adesso.

python
# Eliminare elementi di una lista per indice
numbers = [10, 20, 30, 40, 50]
del numbers[2]  # Rimuovi l'elemento all'indice 2
print(numbers)  # Output: [10, 20, 40, 50]
 
# Eliminare slice di una lista
numbers = [10, 20, 30, 40, 50]
del numbers[1:3]  # Rimuovi elementi dall'indice 1 al 3 (escluso)
print(numbers)  # Output: [10, 40, 50]
 
# Eliminare voci di un dizionario
person = {'name': 'Alice', 'age': 30, 'city': 'Boston'}
del person['age']
print(person)  # Output: {'name': 'Alice', 'city': 'Boston'}
© 2025. Primesoft Co., Ltd.
support@primesoft.ai