2.16. Domeniul de vizibilitate (scope)¶
Când Python caută un nume în interiorul unei funcții, parcurge o secvență specifică de domenii de vizibilitate (scope-uri). Înțelegerea acelei secvențe explică de ce unele atribuiri maschează nume exterioare, de ce altele le modifică și de ce funcțiile imbricate pot reține valori din locul în care au fost definite.
Căutarea unui nume începe în domeniul local al funcției și se deplasează spre exterior, către domeniile de modul și încorporat (built-in), până când se găsește o potrivire.¶
2.16.1. Domeniul local și domeniul de modul¶
Numele definite în interiorul unei funcții sunt locale acelei funcții și dispar la încheierea apelului:
def f():
x = 10
print(x)
f()
print(x) # NameError: x is not defined
Numele definite la nivelul superior al unui fișier .py sunt la nivel de modul (uneori numite globale) și sunt vizibile peste tot în acel fișier, inclusiv în interiorul funcțiilor:
CAMERA = "OpenMV"
def banner():
print("running on", CAMERA)
Citirea unui nume de la nivel de modul din interiorul unei funcții este automată. Atribuirea unei valori acelui nume din interiorul unei funcții nu este – atribuirea creează o nouă variabilă locală care maschează pe cea de la nivel de modul pentru restul apelului:
counter = 0
def bump():
counter = counter + 1 # UnboundLocalError
Atribuirea counter din stânga face ca counter să fie un nume local în bump, așa că citirea din dreapta nu are nicio valoare de găsit.
2.16.1.1. Cuvântul-cheie global¶
Pentru a reatribui efectiv un nume de la nivel de modul din interiorul unei funcții, declară-l mai întâi ca global:
counter = 0
def bump():
global counter
counter += 1
Apelează la global cu parcimonie. Funcțiile care modifică o stare ascunsă sunt mai greu de înțeles decât funcțiile care primesc valori ca argumente și returnează valori noi. Soluția obișnuită pentru „trebuie să partajez o stare” este să transmiți un obiect (o listă, un dicționar, o instanță de clasă) ca argument și să modifici acel obiect în schimb.
2.16.2. Lambda¶
O expresie lambda construiește o mică funcție anonimă într-o singură expresie:
square = lambda x: x * x
square(7) # 49
Este exact echivalentă cu
def square(x):
return x * x
Corpul unei lambda trebuie să fie o singură expresie – fără instrucțiuni, fără mai multe linii. Principala utilizare este transmiterea unei funcții minuscule ca argument către ceva care primește o funcție:
pairs = [("b", 2), ("a", 3), ("c", 1)]
pairs.sort(key=lambda item: item[1])
# [('c', 1), ('b', 2), ('a', 3)]
Când corpul depășește o singură expresie, treci la un def adevărat. Denumirea unei funcții cu def îi conferă, de asemenea, un nume în urmele de execuție (tracebacks), lucru pe care o lambda nu îl are.
2.16.3. Închideri (closures)¶
O funcție definită în interiorul altei funcții poate citi nume din domeniul funcției exterioare. Funcția interioară captează acele nume și continuă să funcționeze chiar și după ce apelul exterior s-a încheiat:
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))
Ieșire:
105 110
add5 și add10 sunt două funcții separate, fiecare reținându-și propriul n. O funcție care construiește și returnează în acest fel o funcție interioară personalizată se numește închidere (closure). Acesta este motivul principal pentru care un limbaj are nevoie de funcții imbricate – o modalitate de a încorpora o anumită stare într-o valoare de tip funcție și apoi de a transmite acea valoare ca un singur element apelabil.
Citirea numelor capturate se face automat. Reasocierea unuia dintre ele necesită un cuvânt-cheie suplimentar. Exemplul de mai jos face ce pare corect și eșuează:
def make_counter():
count = 0
def tick():
count = count + 1 # UnboundLocalError
return count
return tick
Atribuirea către count în interiorul lui tick face ca count să fie local pentru tick, la fel cum l-ar fi făcut local într-o funcție de nivel superior. Cuvântul-cheie nonlocal îi spune lui Python „acest nume trăiește în funcția exterioară, reasociază-l acolo”:
def make_counter():
count = 0
def tick():
nonlocal count
count += 1
return count
return tick
c = make_counter()
print(c(), c(), c())
Ieșire:
1 2 3
nonlocal este pentru domeniul funcției exterioare ceea ce global este pentru domeniul de modul. Reține că modificarea unui obiect capturat (apelarea some_list.append(...), some_dict[k] = v) nu necesită nonlocal – numele nu este reasociat, ci doar obiectul către care indică este modificat.