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.
# 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: 0In 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.
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 definedLe 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:
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.0Entrambe 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:
# 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:
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 UnboundLocalErrorQuesto 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:
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 UnboundLocalErrorLa 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:
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) # NameErrorIl 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:
# 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.80In questo esempio:
tax_rateefree_shipping_thresholdsono valori di configurazione globalisubtotal,tax,shippingetotalsono locali a ogni chiamata dicalculate_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:
- Local (L): l’ambito della funzione corrente
- Enclosing (E): l’ambito di eventuali funzioni contenitrici (funzioni che contengono la funzione corrente)
- Global (G): l’ambito a livello di modulo
- 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:
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.0Quando 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:
# 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.0Quando 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:
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:
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:
- Ambito locale di
inner_function()- non trovato - 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:
# 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: 18Quando Python valuta z + y + multiplier dentro inner():
- L (Local): trova
z = 3 - E (Enclosing): trova
y = 5inouter() - G (Global): trova
multiplier = 10 - 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:
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
globalOgni statement print() vede un value diverso perché Python si ferma alla prima corrispondenza:
- Dentro
inner(): trovavaluelocalmente → stampa "local" - Dentro
outer()ma fuori dainner(): trovavaluenell’ambito diouter()→ stampa "enclosing" - A livello di modulo: trova
valueglobalmente → stampa "global"
Visualizzare l’ordine di ricerca LEGB
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:
- Prevedere i valori delle variabili: sai esattamente quale variabile userà Python
- Evitare conflitti di nomi: capisci quando i nomi si ombreggiano a vicenda
- Progettare funzioni migliori: puoi decidere quale ambito è appropriato per ogni variabile
- 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:
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 UnboundLocalErrorQuesto 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.”
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: 2La 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:
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: 2Sia 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:
# 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: 2Questo 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:
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:
def add_to_total(current_total, value):
return current_total + value
total = 0
total = add_to_total(total, 10) # Chiaro: total viene aggiornatoLa 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:
# 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 == 20Problema 3: le funzioni non sono riutilizzabili
Le funzioni che dipendono da specifiche variabili globali non possono essere riutilizzate facilmente in altri programmi:
# 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:
# 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: 900Usa valori di ritorno:
# 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: 900La seconda versione è più flessibile, testabile e riutilizzabile.
Quando global è davvero appropriata
Esistono usi legittimi di global:
- Configurazione che deve davvero essere globale:
# 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"- Contatori per debugging o statistiche:
# 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 funzionePunti chiave su global
- Usa
globalsolo 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:
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 UnboundLocalErrorPython 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.”
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:
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: 2Ogni 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:
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()globalsi riferisce sempre all’ambito a livello di modulononlocalsi 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:
- Ambito globale (usa
globalinvece):
x = "global"
def func():
nonlocal x # SyntaxError: no binding for nonlocal 'x' found
x = "modified"- Variabili che non esistono in nessun ambito enclosing:
def outer():
def inner():
nonlocal count # SyntaxError: no binding for nonlocal 'count' foundPunti chiave su nonlocal
- Usa
nonlocalper modificare variabili dagli ambiti enclosing delle funzioni nonlocalcerca negli ambiti enclosing delle funzioni, non nell’ambito globale- Leggere variabili enclosing non richiede
nonlocal- solo modificarle lo richiede nonlocalabilita 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:
x = 42
print(x) # Output: 42
del x
# print(x) # NameError: name 'x' is not definedDopo 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:
# 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 definedLa 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:
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.
# 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'}