Vedere subito dopo poche righe messaggi come “name is not defined” o “local variable referenced before assignment” è frustrante. Il codice sembra a posto, ma Python non è d'accordo. La causa è quasi sempre l’ambito (scope): dove un nome è visibile e a quale oggetto si riferisce in quel momento.
Una volta che capisci come Python risolve i nomi—spesso riassunto nella regola LEGB—questi errori diventano prevedibili e facili da correggere. In questo tutorial spiegherò variabili e ambiti in Python passo dopo passo.
Che cos’è una variabile?
Una variabile è un nome associato a un oggetto. In Python, crei un’associazione con l’operatore di assegnazione =. I nomi non sono tipizzati: possono puntare a qualsiasi oggetto (stringa, int, lista, funzione, ecc.).
customer_name ="Ava Torres"
order_count =1
total_cents =1+2*300# evaluated first, then boundOfferto da
Segui queste regole e convenzioni quando assegni i nomi alle variabili per evitare errori di sintassi e bug sottili.
Usa solo lettere, cifre e underscore; i nomi non possono iniziare con una cifra.
Non usare parole chiave come True, for o class come nomi.
Evita di oscurare i built-in come list, dict, sum o max.
Preferisci lo snake_case per la leggibilità (PEP 8).
Questi esempi mostrano cosa non fare e gli errori che Python solleva:
first string value ="First string"# spaces not allowed# SyntaxError: invalid syntax
1st_value =10# cannot start with a digit# SyntaxError: invalid decimal literalTrue="yes"# cannot assign to a keyword# SyntaxError: cannot assign to TrueOfferto da
Come funziona l’ambito in Python (LEGB)
Lo scope è la porzione di codice in cui un nome è visibile. Quando usi un nome, Python lo cerca in questo ordine (LEGB):
Locale: lo scope della funzione corrente.
Enclosing (contenitore): gli scope delle funzioni esterne (per funzioni annidate).
Globale: il livello superiore del modulo (namespace del modulo).
Built-in: i nomi definiti da Python in builtins (ad esempio, len, print).
I nomi vivono in namespace (pensa a dizionari che mappano nomi a oggetti). Lo scope riguarda quali namespace Python consulta per risolvere un nome in un dato punto del codice.
Scope locale
I nomi assegnati dentro una funzione sono locali a quella funzione salvo diversa dichiarazione. Non sono visibili fuori dalla funzione.
defshow_order_id():
order_id =42print("inside function:", order_id)
show_order_id()print("outside function:", order_id)# NameErrorOfferto da
Python decide lo scope in fase di compilazione. Se una funzione assegna a un nome in qualsiasi punto del suo corpo, tutti gli usi di quel nome nella funzione sono trattati come locali—portando a un comune UnboundLocalError quando lo leggi prima di assegnarlo:
discount_rate =0.10# module-level (global)defprice_with_discount(amount_cents):print("configured discount:", discount_rate)# looks local because of the assignment below
discount_rate =0.20# assignment makes 'discount_rate' local in this functionreturnint(amount_cents *(1- discount_rate))# UnboundLocalError: cannot access local variable 'discount_rate' where it is not associated with a valueOfferto da
Per usare il nome a livello di modulo nella funzione, evita di assegnargli oppure contrassegnalo come global (vedi sotto).
Scope contenitore (closure)
Le funzioni annidate possono vedere i nomi della loro funzione immediatamente esterna. Per riassegnare un nome del genere (non solo leggerlo), dichiaralo nonlocal.
defmake_step_counter():
count =0# enclosing scope for 'increment'defincrement():nonlocal count # rebind the 'count' in the nearest enclosing function
count +=1return count
return increment
step = make_step_counter()print(step())# 1print(step())# 2Offerto da
Senza nonlocal, assegnare a count dentro increment() creerebbe un nuovo nome locale lasciando invariato il count esterno.
Scope globale
I nomi assegnati al livello superiore di un modulo vivono nel namespace globale del modulo. Qualsiasi funzione può leggerli. Per assegnare a un nome a livello di modulo da dentro una funzione, dichiaralo global.
greeting ="Hello"defgreet_city(city_name):print(greeting, city_name)# reads globaldefset_greeting(new_greeting):global greeting
greeting = new_greeting # rebinds global
greet_city("Nairobi")# Hello Nairobi
set_greeting("Hi")
greet_city("Nairobi")# Hi NairobiOfferto da
Usa global con parsimonia. Meglio passare valori come parametri e restituire risultati per mantenere il codice testabile e prevedibile.
Scope built-in (e una nota sulle parole chiave)
Lo scope built-in contiene nomi come len, print ed Exception. Evita di oscurarli, altrimenti perderai accesso al built-in in quello scope.
list=[1,2,3]# shadows the built-in 'list' constructorlist("abc")# TypeError: 'list' object is not callabledellist# fix by deleting the shadowing nameOfferto da
Le parole chiave (come if, for, def) fanno parte della sintassi di Python, non del namespace built-in, e non possono mai essere usate come identificatori.
Blocchi, loop e comprehension
I costrutti di blocco e le comprehension di Python hanno comportamenti di scope specifici che spesso sorprendono chi arriva da altri linguaggi.
Non esiste uno scope di blocco per if/for/while/with
Le assegnazioni dentro questi blocchi influiscono sullo scope contenitore (funzione o modulo). Le variabili di loop restano definite anche dopo la fine del ciclo.
ifTrue:
status ="ready"print(status)# "ready"for i inrange(3):passprint(i)# 2 (the last value from the loop)Offerto da
Le comprehension isolano la loro variabile di iterazione
Le list, dict e set comprehension hanno un proprio scope locale per le variabili di loop. La variabile di iterazione non “fuoriesce” nello scope circostante.
numbers =[1,2,3][x for x in numbers]print("x"inglobals()or"x"inlocals())# FalseOfferto da
Python 3.12 (PEP 709) ha inlined le comprehension per velocità mantenendo questa isolazione; ottieni ancora variabili di loop chiare, non “perdenti”, con esecuzione più rapida.
Uso della keyword global
Dichiara un nome come global dentro una funzione quando devi riassegnare una variabile a livello di modulo. Metti la dichiarazione vicino all’inizio della funzione così è chiaro quale variabile stai modificando.
tax_rate =0.08defconfigure_tax(rate):global tax_rate
tax_rate =float(rate)deftotal_with_tax(cents):returnint(cents *(1+ tax_rate))
configure_tax(0.10)print(total_with_tax(1000))# 1100Offerto da
Uso della keyword nonlocal
Usa nonlocal per riassegnare un nome dallo scope della funzione contenitore più vicino. È comune nelle closure che mantengono stato.
defmake_accumulator(start=0):
total = start
defadd(amount):nonlocal total
total += amount
return total
return add
acc = make_accumulator()print(acc(5))# 5print(acc(10))# 15Offerto da
Usare nonlocal per un nome non definito in una funzione contenitore è un SyntaxError. Se il nome è globale, usa global.
locals() e globals() nel 2025
Queste funzioni sono utili per ispezione e debug, non per aggiornare variabili. A partire da Python 3.13 (PEP 667), ogni chiamata a
locals()
in una funzione restituisce un’istantanea indipendente. Modificare quell’istantanea non cambia le variabili locali reali. Ho confermato questo comportamento eseguendo il seguente snippet su Python 3.13:
defprobe():
project ="alpha"
snap1 =locals()
snap1["project"]="beta"# edits the snapshot only
observed = project # still "alpha"
snap2 =locals()# new snapshotreturn snap1["project"], observed, snap2["project"]print(probe())# ('beta', 'alpha', 'alpha')Offerto da
Usa globals() in modo analogo per il namespace del modulo. Quando scrivi codice applicativo, preferisci parametri espliciti e valori di ritorno rispetto a lookup dinamici.
Errori comuni e come risolverli
Questi errori spiegano la maggior parte dei problemi legati allo scope che vedo in pratica.
UnboundLocalError dovuto a un’assegnazione in una funzione: Python tratta un nome come locale se viene assegnato in qualsiasi punto della funzione. Sposta la lettura dopo l’assegnazione, rinomina, oppure aggiungi global/nonlocal a seconda dei casi.
Aspettarsi uno scope di blocco: I nomi assegnati in if/for/while/with persistono nello scope contenitore. Usa funzioni di supporto più piccole per limitare lo scope.
Oscurare i built-in: Evita nomi come list, dict, sum, id o min. Scegli nomi descrittivi come customer_list o min_allowed.
Dimenticare nonlocal nelle closure: Se intendi aggiornare una variabile della funzione esterna, dichiarala nonlocal. Altrimenti crei un nuovo locale e il valore esterno non cambia.
Confondere parole chiave e built-in: Le parole chiave (sintassi) non possono mai essere usate come nomi. I built-in possono essere oscurati ma non dovrebbero esserlo.
Buone pratiche per mantenere semplice lo scope
Queste abitudini rendono il codice più facile da leggere, testare e fare debug.
Preferisci funzioni piccole e mirate. Definisci le variabili nello scope più ristretto possibile.
Passa i dati dentro e fuori tramite parametri e valori di ritorno. Riduci al minimo lo stato a livello di modulo.
Usa le closure in modo intenzionale. Documenta quali nomi esterni una funzione annidata cattura e usa nonlocal quando devi davvero riassegnare.
Scegli nomi descrittivi e non in conflitto. Un underscore iniziale (per esempio, _cache) segnala uso interno.
Tratta locals()/globals() come strumenti di diagnostica in sola lettura, non come meccanismo di configurazione.
Conclusione
Python risolve i nomi cercando negli scope Locale → Contenitore → Globale → Built-in. Capire questo ordine—e quando usare global o nonlocal—elimina errori comuni come NameError e UnboundLocalError. Tieni le variabili nello scope più piccolo possibile, evita di oscurare i built-in e usa le closure con intenzione. Con questo modello mentale, le tue funzioni si comportano in modo prevedibile e lo scope smette di essere una fonte di sorprese.
Ho lavorato in diversi settori e ho ricoperto vari ruoli: sviluppatore software, ricercatore di machine learning, data scientist, product manager. Ma, soprattutto, sono un programmatore a cui piace imparare e condividere conoscenza!
Scopri i 15 migliori server MCP remoti che stanno trasformando lo sviluppo AI nel 2026. Scopri come migliorano automazione, ragionamento, sicurezza e velocità dei workflow.
Vedere subito dopo poche righe messaggi come “name is not defined” o “local variable referenced before assignment” è frustrante. Il codice sembra a posto, ma Python non è d'accordo. La causa è quasi sempre l’ambito (scope): dove un nome è visibile e a quale oggetto si riferisce in quel momento.
Una volta che capisci come Python risolve i nomi—spesso riassunto nella regola LEGB—questi errori diventano prevedibili e facili da correggere. In questo tutorial spiegherò variabili e ambiti in Python passo dopo passo.
Che cos’è una variabile?
Una variabile è un nome associato a un oggetto. In Python, crei un’associazione con l’operatore di assegnazione
=. I nomi non sono tipizzati: possono puntare a qualsiasi oggetto (stringa, int, lista, funzione, ecc.).Segui queste regole e convenzioni quando assegni i nomi alle variabili per evitare errori di sintassi e bug sottili.
True,foroclasscome nomi.list,dict,sumomax.snake_caseper la leggibilità (PEP 8).Questi esempi mostrano cosa non fare e gli errori che Python solleva:
Come funziona l’ambito in Python (
LEGB)Lo scope è la porzione di codice in cui un nome è visibile. Quando usi un nome, Python lo cerca in questo ordine (LEGB):
builtins(ad esempio,len,print).I nomi vivono in namespace (pensa a dizionari che mappano nomi a oggetti). Lo scope riguarda quali namespace Python consulta per risolvere un nome in un dato punto del codice.
Scope locale
I nomi assegnati dentro una funzione sono locali a quella funzione salvo diversa dichiarazione. Non sono visibili fuori dalla funzione.
Python decide lo scope in fase di compilazione. Se una funzione assegna a un nome in qualsiasi punto del suo corpo, tutti gli usi di quel nome nella funzione sono trattati come locali—portando a un comune
UnboundLocalErrorquando lo leggi prima di assegnarlo:Per usare il nome a livello di modulo nella funzione, evita di assegnargli oppure contrassegnalo come
global(vedi sotto).Scope contenitore (closure)
Le funzioni annidate possono vedere i nomi della loro funzione immediatamente esterna. Per riassegnare un nome del genere (non solo leggerlo), dichiaralo
nonlocal.Senza
nonlocal, assegnare acountdentroincrement()creerebbe un nuovo nome locale lasciando invariato ilcountesterno.Scope globale
I nomi assegnati al livello superiore di un modulo vivono nel namespace globale del modulo. Qualsiasi funzione può leggerli. Per assegnare a un nome a livello di modulo da dentro una funzione, dichiaralo
global.Usa
globalcon parsimonia. Meglio passare valori come parametri e restituire risultati per mantenere il codice testabile e prevedibile.Scope built-in (e una nota sulle parole chiave)
Lo scope built-in contiene nomi come
len,printedException. Evita di oscurarli, altrimenti perderai accesso al built-in in quello scope.Le parole chiave (come
if,for,def) fanno parte della sintassi di Python, non del namespace built-in, e non possono mai essere usate come identificatori.Blocchi, loop e comprehension
I costrutti di blocco e le comprehension di Python hanno comportamenti di scope specifici che spesso sorprendono chi arriva da altri linguaggi.
Non esiste uno scope di blocco per
if/for/while/withLe assegnazioni dentro questi blocchi influiscono sullo scope contenitore (funzione o modulo). Le variabili di loop restano definite anche dopo la fine del ciclo.
Le comprehension isolano la loro variabile di iterazione
Le list, dict e set comprehension hanno un proprio scope locale per le variabili di loop. La variabile di iterazione non “fuoriesce” nello scope circostante.
Python 3.12 (PEP 709) ha inlined le comprehension per velocità mantenendo questa isolazione; ottieni ancora variabili di loop chiare, non “perdenti”, con esecuzione più rapida.
Uso della keyword
globalDichiara un nome come
globaldentro una funzione quando devi riassegnare una variabile a livello di modulo. Metti la dichiarazione vicino all’inizio della funzione così è chiaro quale variabile stai modificando.Uso della keyword
nonlocalUsa
nonlocalper riassegnare un nome dallo scope della funzione contenitore più vicino. È comune nelle closure che mantengono stato.Usare
nonlocalper un nome non definito in una funzione contenitore è unSyntaxError. Se il nome è globale, usaglobal.locals()eglobals()nel 2025Queste funzioni sono utili per ispezione e debug, non per aggiornare variabili. A partire da Python 3.13 (PEP 667), ogni chiamata a
locals()
in una funzione restituisce un’istantanea indipendente. Modificare quell’istantanea non cambia le variabili locali reali. Ho confermato questo comportamento eseguendo il seguente snippet su Python 3.13:
Usa
globals()in modo analogo per il namespace del modulo. Quando scrivi codice applicativo, preferisci parametri espliciti e valori di ritorno rispetto a lookup dinamici.Errori comuni e come risolverli
Questi errori spiegano la maggior parte dei problemi legati allo scope che vedo in pratica.
UnboundLocalErrordovuto a un’assegnazione in una funzione: Python tratta un nome come locale se viene assegnato in qualsiasi punto della funzione. Sposta la lettura dopo l’assegnazione, rinomina, oppure aggiungi global/nonlocal a seconda dei casi.customer_listomin_allowed.nonlocalnelle closure: Se intendi aggiornare una variabile della funzione esterna, dichiarala nonlocal. Altrimenti crei un nuovo locale e il valore esterno non cambia.Buone pratiche per mantenere semplice lo scope
Queste abitudini rendono il codice più facile da leggere, testare e fare debug.
_cache) segnala uso interno.Conclusione
Python risolve i nomi cercando negli scope Locale → Contenitore → Globale → Built-in. Capire questo ordine—e quando usare global o nonlocal—elimina errori comuni come
NameErroreUnboundLocalError. Tieni le variabili nello scope più piccolo possibile, evita di oscurare i built-in e usa le closure con intenzione. Con questo modello mentale, le tue funzioni si comportano in modo prevedibile e lo scope smette di essere una fonte di sorprese.