2.16. Ambito¶
Quando Python cerca un nome all’interno di una funzione, esamina una sequenza specifica di ambiti. Capire questa sequenza spiega perché alcune assegnazioni mascherano i nomi esterni, perché altre li modificano e perché le funzioni annidate possono ricordare i valori del punto in cui sono state definite.
La ricerca dei nomi inizia nell’ambito locale della funzione e procede verso l’esterno, attraverso l’ambito di modulo e quello dei builtin, finché non viene trovata una corrispondenza.¶
2.16.1. Ambito locale e ambito di modulo¶
I nomi definiti all’interno di una funzione sono locali a quella funzione e scompaiono al termine della chiamata:
def f():
x = 10
print(x)
f()
print(x) # NameError: x is not defined
I nomi definiti al livello superiore di un file .py sono a livello di modulo (a volte chiamati globali) e sono visibili ovunque in quel file, anche all’interno delle funzioni:
CAMERA = "OpenMV"
def banner():
print("running on", CAMERA)
La lettura di un nome a livello di modulo dall’interno di una funzione è automatica. L”assegnazione a quel nome dall’interno di una funzione non lo è: l’assegnazione crea una nuova variabile locale che maschera quella a livello di modulo per il resto della chiamata:
counter = 0
def bump():
counter = counter + 1 # UnboundLocalError
Il counter a sinistra rende counter un nome locale in bump, quindi la lettura a destra non trova alcun valore.
2.16.1.1. La parola chiave global¶
Per riassegnare davvero un nome a livello di modulo dall’interno di una funzione, dichiaralo prima come global:
counter = 0
def bump():
global counter
counter += 1
Ricorri a global con parsimonia. Le funzioni che modificano uno stato nascosto sono più difficili da comprendere rispetto a quelle che ricevono valori come argomenti e restituiscono nuovi valori. La soluzione abituale per «devo condividere uno stato» è passare un oggetto (una lista, un dict, un’istanza di classe) come argomento e modificare quello.
2.16.2. Lambda¶
Una lambda costruisce una piccola funzione anonima in un’unica espressione:
square = lambda x: x * x
square(7) # 49
È esattamente equivalente a
def square(x):
return x * x
Il corpo di una lambda deve essere un’unica espressione: niente istruzioni, niente righe multiple. L’uso principale è passare una funzione minuscola come argomento a qualcosa che accetta una funzione:
pairs = [("b", 2), ("a", 3), ("c", 1)]
pairs.sort(key=lambda item: item[1])
# [('c', 1), ('b', 2), ('a', 3)]
Quando il corpo cresce oltre una singola espressione, passa a un vero def. Dare un nome a una funzione con def le assegna anche un nome nei traceback, cosa che una lambda non ha.
2.16.3. Closure¶
Una funzione definita all’interno di un’altra funzione può leggere i nomi dall’ambito della funzione che la racchiude. La funzione interna cattura quei nomi e continua a funzionare anche dopo che la chiamata esterna è terminata:
def make_adder(n):
def add(x):
return x + n
return add
add5 = make_adder(5)
add10 = make_adder(10)
print(add5(100), add10(100))
Output:
105 110
add5 e add10 sono due funzioni distinte, ciascuna delle quali ricorda il proprio n. Una funzione che costruisce e restituisce in questo modo una funzione interna personalizzata si chiama closure. È il motivo principale per cui un linguaggio ha bisogno delle funzioni annidate: un modo per incorporare uno stato in un valore-funzione e poi consegnare quel valore come un unico callable.
La lettura dei nomi catturati avviene automaticamente. La riassegnazione di uno di essi richiede una parola chiave aggiuntiva. L’esempio seguente fa ciò che sembra corretto, e fallisce:
def make_counter():
count = 0
def tick():
count = count + 1 # UnboundLocalError
return count
return tick
L’assegnazione a count all’interno di tick rende count locale a tick, esattamente come lo avrebbe reso locale in una funzione di livello superiore. La parola chiave nonlocal dice a Python «questo nome risiede nella funzione che racchiude, riassegnalo lì»:
def make_counter():
count = 0
def tick():
nonlocal count
count += 1
return count
return tick
c = make_counter()
print(c(), c(), c())
Output:
1 2 3
nonlocal sta all’ambito della funzione che racchiude come global sta all’ambito di modulo. Nota che la modifica di un oggetto catturato (chiamando some_list.append(...), some_dict[k] = v) non richiede nonlocal: il nome non viene riassegnato, viene modificato solo l’oggetto a cui punta.