2.16. Rozsah platnosti

Když Python vyhledává název uvnitř funkce, prohledává určitou posloupnost rozsahů platnosti. Pochopení této posloupnosti vysvětluje, proč některá přiřazení zastiňují vnější názvy, proč jiná je modifikují a proč si vnořené funkce mohou pamatovat hodnoty z místa, kde byly definovány.

Vnořené krabice znázorňující lokální rozsah uvnitř modulového rozsahu uvnitř vestavěného rozsahu, se šipkou naznačující, že vyhledávání názvu postupuje směrem ven z nejvnitřnějšího rámce.

Vyhledávání názvu začíná v lokálním rozsahu funkce a postupuje směrem ven do modulových a vestavěných rozsahů, dokud není nalezena shoda.

2.16.1. Lokální a modulový rozsah

Názvy definované uvnitř funkce jsou lokální pro tuto funkci a po skončení volání zmizí:

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

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

Názvy definované na nejvyšší úrovni souboru .py jsou na úrovni modulu (někdy nazývané globální) a jsou viditelné všude v daném souboru, včetně uvnitř funkcí:

CAMERA = "OpenMV"

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

Čtení názvu na úrovni modulu zevnitř funkce je automatické. Přiřazení k tomuto názvu zevnitř funkce nikoli – přiřazení vytvoří novou lokální proměnnou, která modulovou proměnnou po zbytek volání zastíní:

counter = 0

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

Levé counter činí z counter lokální název ve funkci bump, takže čtení vpravo nemá žádnou hodnotu, kterou by našlo.

2.16.1.1. Klíčové slovo global

Chcete-li skutečně znovu přiřadit název na úrovni modulu zevnitř funkce, nejprve jej deklarujte jako global:

counter = 0

def bump():
    global counter
    counter += 1

Po global sahejte jen zřídka. Funkce, které mění skrytý stav, se hůře chápou než funkce, které přijímají hodnoty jako argumenty a vracejí nové hodnoty ven. Obvyklým řešením pro „potřebuji sdílet stav“ je předat objekt (seznam, slovník, instanci třídy) jako argument a měnit jej místo toho.

2.16.2. Lambdy

lambda vytváří malou anonymní funkci v jediném výrazu:

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

Je přesně ekvivalentní

def square(x):
    return x * x

Tělo lambda musí být jediný výraz – žádné příkazy, žádné více řádků. Hlavním použitím je předání drobné funkce jako argumentu něčemu, co funkci přijímá:

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

Když tělo přeroste jeden výraz, přejděte na skutečné def. Pojmenování funkce pomocí def jí také dá název ve výpisech zásobníku, který lambda nemá.

2.16.3. Uzávěry

Funkce definovaná uvnitř jiné funkce může číst názvy z rozsahu obklopující funkce. Vnitřní funkce tyto názvy zachytí a funguje dál i poté, co se vnější volání vrátilo:

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

Výstup:

105 110

add5 a add10 jsou dvě samostatné funkce, z nichž každá si pamatuje své vlastní n. Funkce, která tímto způsobem vytváří a vrací přizpůsobenou vnitřní funkci, se nazývá uzávěr. To je hlavní důvod, proč jazyk vůbec potřebuje vnořené funkce – způsob, jak do hodnoty funkce zapéct nějaký stav a poté tuto hodnotu předat jako jediný volatelný objekt.

Čtení zachycených názvů probíhá automaticky. Opětovné navázání některého vyžaduje další klíčové slovo. Níže uvedený příklad dělá to, co vypadá správně, a selže:

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

Přiřazení k count uvnitř tick činí count lokální pro tick stejným způsobem, jako by jej učinilo lokální ve funkci nejvyšší úrovně. Klíčové slovo nonlocal říká Pythonu „tento název žije v obklopující funkci, znovu jej navaž tam“:

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

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

Výstup:

1 2 3

nonlocal je pro rozsah obklopující funkce tím, čím je global pro modulový rozsah. Všimněte si, že mutace zachyceného objektu (volání some_list.append(...), some_dict[k] = v) nonlocal nepotřebuje – název není znovu navazován, mění se pouze objekt, na který ukazuje.