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.

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.

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.