2.16. Doseg (scope)¶
Kada Python traži ime unutar funkcije, pretražuje određeni niz dosega (scopeova). Razumijevanje tog niza objašnjava zašto neke dodjele zasjenjuju vanjska imena, zašto ih druge mijenjaju i zašto ugniježđene funkcije mogu pamtiti vrijednosti s mjesta gdje su definirane.
Pretraživanje imena počinje u lokalnom dosegu funkcije i ide prema van do dosega modula i ugrađenog dosega dok se ne pronađe podudaranje.¶
2.16.1. Lokalni doseg i doseg modula¶
Imena definirana unutar funkcije lokalna su za tu funkciju i nestaju kada poziv završi:
def f():
x = 10
print(x)
f()
print(x) # NameError: x is not defined
Imena definirana na najvišoj razini .py datoteke su na razini modula (ponekad zvana globalna) i vidljiva su svugdje u toj datoteci, uključujući unutar funkcija:
CAMERA = "OpenMV"
def banner():
print("running on", CAMERA)
Čitanje imena na razini modula iz unutrašnjosti funkcije je automatsko. Dodjeljivanje tom imenu iz unutrašnjosti funkcije nije – dodjela stvara novu lokalnu varijablu koja zasjenjuje onu na razini modula do kraja poziva:
counter = 0
def bump():
counter = counter + 1 # UnboundLocalError
Lijevi counter čini counter lokalnim imenom u bump, pa čitanje s desne strane nema vrijednost koju bi pronašlo.
2.16.1.1. Ključna riječ global¶
Da biste stvarno ponovno dodijelili ime na razini modula iz unutrašnjosti funkcije, prvo ga deklarirajte kao global:
counter = 0
def bump():
global counter
counter += 1
Posežite za global štedljivo. Funkcije koje mijenjaju skriveno stanje teže je razumjeti od funkcija koje primaju vrijednosti kao argumente i vraćaju nove vrijednosti. Uobičajeno rješenje za „trebam podijeliti stanje” jest proslijediti objekt (listu, dict, instancu klase) kao argument i mijenjati njega.
2.16.2. Lambda izrazi¶
lambda gradi malu anonimnu funkciju u jednom izrazu:
square = lambda x: x * x
square(7) # 49
Točno je ekvivalentna
def square(x):
return x * x
Tijelo lambda mora biti jedan izraz – bez naredbi, bez više redaka. Glavna uporaba je prosljeđivanje sićušne funkcije kao argumenta nečemu što prima funkciju:
pairs = [("b", 2), ("a", 3), ("c", 1)]
pairs.sort(key=lambda item: item[1])
# [('c', 1), ('b', 2), ('a', 3)]
Kada tijelo naraste preko jednog izraza, prijeđite na pravi def. Imenovanje funkcije s def također joj daje ime u praćenjima poziva (traceback), što lambda nema.
2.16.3. Zatvorenja (closures)¶
Funkcija definirana unutar druge funkcije može čitati imena iz dosega obuhvaćajuće funkcije. Unutarnja funkcija zarobljava ta imena i nastavlja raditi čak i nakon što se vanjski poziv vratio:
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))
Ispis:
105 110
add5 i add10 dvije su odvojene funkcije, svaka pamti svoj vlastiti n. Funkcija koja na ovaj način gradi i vraća prilagođenu unutarnju funkciju naziva se zatvorenje (closure). To je glavni razlog zašto jeziku uopće trebaju ugniježđene funkcije – način da se neko stanje ugradi u vrijednost funkcije, a zatim tu vrijednost preda kao jedan pozivni objekt.
Čitanje zarobljenih imena događa se automatski. Ponovno vezanje jednog od njih treba dodatnu ključnu riječ. Primjer u nastavku radi ono što izgleda ispravno i ne uspijeva:
def make_counter():
count = 0
def tick():
count = count + 1 # UnboundLocalError
return count
return tick
Dodjela imenu count unutar tick čini count lokalnim za tick, na isti način kao što bi ga učinila lokalnim u funkciji najviše razine. Ključna riječ nonlocal govori Pythonu „ovo ime živi u obuhvaćajućoj funkciji, ponovno ga veži tamo”:
def make_counter():
count = 0
def tick():
nonlocal count
count += 1
return count
return tick
c = make_counter()
print(c(), c(), c())
Ispis:
1 2 3
nonlocal je za doseg obuhvaćajuće funkcije ono što je global za doseg modula. Imajte na umu da mijenjanje zarobljenog objekta (poziv some_list.append(...), some_dict[k] = v) ne treba nonlocal – ime se ne veže ponovno, mijenja se samo objekt na koji pokazuje.