2.16. Hatókör¶
Amikor a Python egy nevet keres egy függvényen belül, egy meghatározott hatókör-sorozatban kutat. Ennek a sorrendnek a megértése megmagyarázza, miért árnyékolnak egyes hozzárendelések külső neveket, mások miért módosítják azokat, és miért tudnak a beágyazott függvények emlékezni a definiálásuk helyéről származó értékekre.
A névkeresés a lokális függvényhatókörben indul, és kifelé halad a modul- és beépített hatókörökhöz, amíg egyezést nem talál.¶
2.16.1. Lokális és modulhatókör¶
A függvényen belül definiált nevek lokálisak az adott függvényre nézve, és eltűnnek, amikor a hívás véget ér:
def f():
x = 10
print(x)
f()
print(x) # NameError: x is not defined
Egy .py fájl legfelső szintjén definiált nevek modulszintűek (néha globálisnak nevezik), és láthatóak az adott fájl mindenhol, beleértve a függvényeken belül is:
CAMERA = "OpenMV"
def banner():
print("running on", CAMERA)
Egy modulszintű név olvasása egy függvényen belülről automatikus. Az adott névhez való hozzárendelés egy függvényen belülről nem – a hozzárendelés egy új lokális változót hoz létre, amely a hívás hátralévő részében elárnyékolja a modulszintűt:
counter = 0
def bump():
counter = counter + 1 # UnboundLocalError
A bal oldali counter a counter nevet lokális névvé teszi a bump függvényben, így a jobb oldali olvasásnak nincs megtalálható értéke.
2.16.1.1. A global kulcsszó¶
Ahhoz, hogy egy modulszintű nevet egy függvényen belülről ténylegesen újra hozzárendelj, először global kulcsszóval kell deklarálnod:
counter = 0
def bump():
global counter
counter += 1
A global kulcsszót takarékosan használd. A rejtett állapotot módosító függvényekről nehezebb gondolkodni, mint azokról, amelyek értékeket vesznek be argumentumként, és új értékeket adnak vissza. A „meg kell osztanom az állapotot” szokásos megoldása az, hogy egy objektumot (egy listát, egy dict-et, egy osztálypéldányt) adsz át argumentumként, és azt módosítod helyette.
2.16.2. Lambdák¶
Egy lambda egyetlen kifejezésben épít fel egy kis névtelen függvényt:
square = lambda x: x * x
square(7) # 49
Pontosan egyenértékű ezzel:
def square(x):
return x * x
Egy lambda törzsének egyetlen kifejezésnek kell lennie – nincsenek utasítások, nincsenek több sorok. A fő felhasználása egy apró függvény átadása argumentumként valaminek, ami egy függvényt vesz át:
pairs = [("b", 2), ("a", 3), ("c", 1)]
pairs.sort(key=lambda item: item[1])
# [('c', 1), ('b', 2), ('a', 3)]
Amikor a törzs egy kifejezésnél nagyobbra nő, válts át egy igazi def definícióra. Egy függvény def segítségével történő elnevezése a visszakövetésekben (traceback) is nevet ad neki, amivel egy lambda nem rendelkezik.
2.16.3. Lezárások (closure-ök)¶
Egy másik függvényen belül definiált függvény olvasni tudja a neveket a körülvevő függvény hatóköréből. A belső függvény megragadja ezeket a neveket, és tovább működik, még azután is, hogy a külső hívás visszatért:
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))
Kimenet:
105 110
Az add5 és az add10 két különálló függvény, mindegyik a saját n értékére emlékszik. Egy olyan függvényt, amely ilyen módon egy testreszabott belső függvényt épít fel és ad vissza, lezárásnak (closure) nevezünk. Ez a fő oka annak, hogy egy nyelvnek egyáltalán szüksége van beágyazott függvényekre – egy mód arra, hogy némi állapotot süssünk bele egy függvényértékbe, majd ezt az értéket egyetlen meghívható dologként adjuk tovább.
A megragadott nevek olvasása automatikusan történik. Egyikük újra hozzárendelése egy extra kulcsszót igényel. Az alábbi példa azt teszi, ami helyesnek tűnik, és elbukik:
def make_counter():
count = 0
def tick():
count = count + 1 # UnboundLocalError
return count
return tick
A count névhez való hozzárendelés a tick függvényen belül a count nevet lokálissá teszi a tick függvényre nézve, ugyanúgy, ahogy egy legfelső szintű függvényben is lokálissá tette volna. A nonlocal kulcsszó azt mondja a Pythonnak: „ez a név a körülvevő függvényben él, ott rendeld újra hozzá”:
def make_counter():
count = 0
def tick():
nonlocal count
count += 1
return count
return tick
c = make_counter()
print(c(), c(), c())
Kimenet:
1 2 3
A nonlocal a körülvevő függvény hatókörének az, ami a global a modulhatókörnek. Vedd figyelembe, hogy egy megragadott objektum módosítása (a some_list.append(...), some_dict[k] = v meghívása) nem igényel nonlocal kulcsszót – a név nem kerül újra hozzárendelésre, csak az objektum változik, amelyre mutat.