2.16. Näkyvyysalue

Kun Python etsii nimeä funktion sisältä, se käy läpi tietyn näkyvyysalueiden sarjan. Tuon sarjan ymmärtäminen selittää, miksi jotkin sijoitukset varjostavat ulompia nimiä, miksi toiset muuttavat niitä ja miksi sisäkkäiset funktiot voivat muistaa arvoja siitä, missä ne määriteltiin.

Sisäkkäiset laatikot, jotka esittävät paikallista näkyvyysaluetta moduulinäkyvyysalueen sisällä sisäänrakennetun näkyvyysalueen sisällä, sekä nuoli, joka osoittaa, että nimihaku etenee ulospäin sisimmästä kehyksestä.

Nimihaku alkaa paikallisesta funktion näkyvyysalueesta ja etenee ulospäin moduuli- ja sisäänrakennettuihin näkyvyysalueisiin, kunnes osuma löytyy.

2.16.1. Paikallinen ja moduulin näkyvyysalue

Funktion sisällä määritellyt nimet ovat paikallisia kyseiselle funktiolle ja katoavat, kun kutsu päättyy:

def f():
    x = 10
    print(x)

f()
print(x)              # NameError: x is not defined

.py-tiedoston ylimmällä tasolla määritellyt nimet ovat moduulitason (joskus globaaleiksi kutsuttuja) ja näkyvät kaikkialla kyseisessä tiedostossa, myös funktioiden sisällä:

CAMERA = "OpenMV"

def banner():
    print("running on", CAMERA)

Moduulitason nimen lukeminen funktion sisältä tapahtuu automaattisesti. Tuon nimen sijoittaminen funktion sisältä ei – sijoitus luo uuden paikallisen muuttujan, joka varjostaa moduulitasoista loppukutsun ajaksi:

counter = 0

def bump():
    counter = counter + 1     # UnboundLocalError

Vasemmanpuoleinen counter tekee counter-nimestä paikallisen bump-funktiossa, joten oikealla olevalle luvulle ei ole arvoa löydettäväksi.

2.16.1.1. global-avainsana

Sijoittaaksesi moduulitason nimen todella uudelleen funktion sisältä, määrittele se ensin global-sanalla:

counter = 0

def bump():
    global counter
    counter += 1

Käytä global-sanaa säästeliäästi. Funktiot, jotka muuttavat piilotettua tilaa, ovat vaikeampia hahmottaa kuin funktiot, jotka ottavat arvoja sisään argumentteina ja palauttavat uusia arvoja ulos. Tavanomainen korjaus ongelmaan ”minun täytyy jakaa tilaa” on välittää objekti (lista, sanakirja, luokan ilmentymä) argumenttina ja muuttaa sitä sen sijaan.

2.16.2. Lambdat

lambda rakentaa pienen anonyymin funktion yhdessä lausekkeessa:

square = lambda x: x * x
square(7)             # 49

Se vastaa täsmälleen seuraavaa

def square(x):
    return x * x

lambda-funktion runko täytyy olla yksittäinen lauseke – ei lauseita, ei useita rivejä. Pääkäyttö on pienen funktion välittäminen argumenttina jollekin, joka ottaa funktion:

pairs = [("b", 2), ("a", 3), ("c", 1)]
pairs.sort(key=lambda item: item[1])
# [('c', 1), ('b', 2), ('a', 3)]

Kun runko kasvaa yli yhden lausekkeen, vaihda oikeaan def-määrittelyyn. Funktion nimeäminen def-sanalla antaa sille myös nimen jäljityskertomuksissa, jota lambda-funktiolla ei ole.

2.16.3. Sulkeumat

Toisen funktion sisällä määritelty funktio voi lukea nimiä ympäröivän funktion näkyvyysalueesta. Sisäfunktio kaappaa nuo nimet ja jatkaa toimintaansa myös sen jälkeen, kun ulompi kutsu on palannut:

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))

Tuloste:

105 110

add5 ja add10 ovat kaksi erillistä funktiota, joista kumpikin muistaa oman n-arvonsa. Funktiota, joka rakentaa ja palauttaa tällä tavoin mukautetun sisäfunktion, kutsutaan sulkeumaksi. Se on pääsyy siihen, miksi kieli ylipäätään tarvitsee sisäkkäisiä funktioita – tapa leipoa jokin tila funktioarvoon ja luovuttaa tuo arvo sitten yhtenä kutsuttavana.

Kaapattujen nimien lukeminen tapahtuu automaattisesti. Yhden uudelleensitominen tarvitsee lisäavainsanan. Alla oleva esimerkki tekee sen, mikä näyttää oikealta, ja epäonnistuu:

def make_counter():
    count = 0
    def tick():
        count = count + 1     # UnboundLocalError
        return count
    return tick

Sijoitus count-nimeen tick-funktion sisällä tekee count-nimestä paikallisen tick-funktiolle, samalla tavalla kuin se olisi tehnyt siitä paikallisen ylimmän tason funktiossa. nonlocal-avainsana kertoo Pythonille ”tämä nimi elää ympäröivässä funktiossa, sido se uudelleen siellä”:

def make_counter():
    count = 0
    def tick():
        nonlocal count
        count += 1
        return count
    return tick

c = make_counter()
print(c(), c(), c())

Tuloste:

1 2 3

nonlocal on ympäröivän funktion näkyvyysalueelle se, mitä global on moduulin näkyvyysalueelle. Huomaa, että kaapatun objektin muuttaminen (some_list.append(...)- tai some_dict[k] = v-kutsu) ei tarvitse nonlocal-sanaa – nimeä ei sidota uudelleen, vaan vain objekti, johon se osoittaa, muuttuu.