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.

Nested boxes showing a local scope inside a module scope inside the built-in scope, with an arrow indicating that name lookup walks outward from the innermost frame.

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.