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.

Ugniježđene kutije koje prikazuju lokalni doseg unutar dosega modula unutar ugrađenog dosega, sa strelicom koja označava da pretraživanje imena ide prema van od najunutarnjeg okvira.

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.