2.16. Räckvidd¶
När Python slår upp ett namn inuti en funktion söker det i en specifik sekvens av räckvidder. Att förstå den sekvensen förklarar varför vissa tilldelningar skuggar yttre namn, varför andra ändrar dem och varför nästlade funktioner kan komma ihåg värden från där de definierades.
Namnuppslagning börjar i den lokala funktionsräckvidden och vandrar utåt till modul- och inbyggda räckvidder tills en träff hittas.¶
2.16.1. Lokal räckvidd och modulräckvidd¶
Namn som definieras inuti en funktion är lokala för den funktionen och försvinner när anropet avslutas:
def f():
x = 10
print(x)
f()
print(x) # NameError: x is not defined
Namn som definieras på toppnivån i en .py-fil är på modulnivå (ibland kallade globala) och är synliga överallt i den filen, inklusive inuti funktioner:
CAMERA = "OpenMV"
def banner():
print("running on", CAMERA)
Att läsa ett namn på modulnivå inifrån en funktion sker automatiskt. Att tilldela det namnet inifrån en funktion gör det inte – tilldelningen skapar en ny lokal variabel som skuggar den på modulnivå under resten av anropet:
counter = 0
def bump():
counter = counter + 1 # UnboundLocalError
Det vänstra counter gör counter till ett lokalt namn i bump, så läsningen till höger har inget värde att hitta.
2.16.1.1. Nyckelordet global¶
För att faktiskt omtilldela ett namn på modulnivå inifrån en funktion, deklarera det som global först:
counter = 0
def bump():
global counter
counter += 1
Använd global sparsamt. Funktioner som muterar dolt tillstånd är svårare att resonera om än funktioner som tar in värden som argument och returnerar nya värden. Den vanliga lösningen på ”jag behöver dela tillstånd” är att skicka ett objekt (en lista, en dict, en klassinstans) som ett argument och mutera det i stället.
2.16.2. Lambdas¶
En lambda bygger en liten anonym funktion i ett enda uttryck:
square = lambda x: x * x
square(7) # 49
Den är exakt likvärdig med
def square(x):
return x * x
Kroppen i en lambda måste vara ett enda uttryck – inga satser, inga flera rader. Den huvudsakliga användningen är att skicka en pytteliten funktion som argument till något som tar emot en funktion:
pairs = [("b", 2), ("a", 3), ("c", 1)]
pairs.sort(key=lambda item: item[1])
# [('c', 1), ('b', 2), ('a', 3)]
När kroppen växer förbi ett uttryck, byt till en riktig def. Att namnge en funktion med def ger den också ett namn i spårningar, vilket en lambda inte har.
2.16.3. Slutningar¶
En funktion som definieras inuti en annan funktion kan läsa namn från den omslutande funktionens räckvidd. Den inre funktionen fångar dessa namn och fortsätter att fungera även efter att det yttre anropet har returnerat:
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))
Utdata:
105 110
add5 och add10 är två separata funktioner, var och en kommer ihåg sitt eget n. En funktion som bygger och returnerar en anpassad inre funktion på detta sätt kallas en slutning (closure). Det är den främsta anledningen till att ett språk behöver nästlade funktioner i första hand – ett sätt att baka in en del tillstånd i ett funktionsvärde och sedan lämna över det värdet som ett enda anropbart objekt.
Att läsa fångade namn sker automatiskt. Att ombinda ett kräver ett extra nyckelord. Exemplet nedan gör det som ser rätt ut och misslyckas:
def make_counter():
count = 0
def tick():
count = count + 1 # UnboundLocalError
return count
return tick
Tilldelningen till count inuti tick gör count lokalt för tick, på samma sätt som det skulle ha gjort det lokalt i en funktion på toppnivå. Nyckelordet nonlocal säger åt Python ”detta namn finns i den omslutande funktionen, ombind det där”:
def make_counter():
count = 0
def tick():
nonlocal count
count += 1
return count
return tick
c = make_counter()
print(c(), c(), c())
Utdata:
1 2 3
nonlocal är till räckvidden för omslutande funktioner vad global är till modulräckvidd. Observera att mutering av ett fångat objekt (att anropa some_list.append(...), some_dict[k] = v) inte kräver nonlocal – namnet ombinds inte, bara objektet det pekar på ändras.