2.16. Область видимості¶
Коли Python шукає назву всередині функції, він проходить конкретну послідовність областей видимості. Розуміння цієї послідовності пояснює, чому одні присвоєння затіняють зовнішні назви, чому інші їх модифікують, і чому вкладені функції можуть пам’ятати значення з місця їхнього визначення.
Пошук назви починається в локальній області видимості функції і рухається назовні до областей видимості модуля та вбудованих, поки не знайдено збіг.¶
2.16.1. Локальна та модульна область видимості¶
Назви, визначені всередині функції, є локальними для цієї функції і зникають після завершення виклику:
def f():
x = 10
print(x)
f()
print(x) # NameError: x is not defined
Назви, визначені на верхньому рівні файлу .py, є рівня модуля (іноді їх називають глобальними) і видимі скрізь у цьому файлі, включно з функціями:
CAMERA = "OpenMV"
def banner():
print("running on", CAMERA)
Читання назви рівня модуля зсередини функції відбувається автоматично. Присвоєння цій назві зсередини функції – ні: присвоєння створює нову локальну змінну, яка затіняє назву рівня модуля на час виклику:
counter = 0
def bump():
counter = counter + 1 # UnboundLocalError
counter у лівій частині робить counter локальною назвою у bump, тому читання праворуч не знаходить жодного значення.
2.16.1.1. Ключове слово global¶
Щоб фактично перевизначити назву рівня модуля зсередини функції, спочатку оголосіть її global:
counter = 0
def bump():
global counter
counter += 1
Використовуйте global ощадливо. Функції, що змінюють приховані стани, важче осмислювати, ніж функції, що приймають значення як аргументи і повертають нові значення. Звичайне вирішення «потрібно поділитися станом» – передати об’єкт (список, словник, екземпляр класу) як аргумент і змінювати його.
2.16.2. Лямбда-вирази¶
lambda будує невелику анонімну функцію в одному виразі:
square = lambda x: x * x
square(7) # 49
Це точно еквівалентно
def square(x):
return x * x
Тіло lambda має бути одним виразом – без операторів, без кількох рядків. Основне використання – передача невеликої функції як аргументу до чогось, що приймає функцію:
pairs = [("b", 2), ("a", 3), ("c", 1)]
pairs.sort(key=lambda item: item[1])
# [('c', 1), ('b', 2), ('a', 3)]
Коли тіло розростається більш ніж на один вираз, переходьте до справжнього def. Іменування функції через def також дає їй назву у трасуваннях стека, чого lambda не має.
2.16.3. Замикання¶
Функція, визначена всередині іншої функції, може читати назви з області видимості зовнішньої функції. Внутрішня функція захоплює ці назви і продовжує працювати навіть після завершення зовнішнього виклику:
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))
Виведення:
105 110
add5 і add10 – це дві окремі функції, кожна пам’ятає своє n. Функція, яка таким чином будує і повертає налаштовану внутрішню функцію, називається замиканням. Це головна причина, чому мові потрібні вкладені функції – спосіб запекти певний стан у значення функції, а потім передати це значення як єдиний об’єкт, що можна викликати.
Читання захоплених назв відбувається автоматично. Перепризначення однієї потребує додаткового ключового слова. Наведений нижче приклад виглядає правильним, але не спрацьовує:
def make_counter():
count = 0
def tick():
count = count + 1 # UnboundLocalError
return count
return tick
Присвоєння count всередині tick робить count локальним для tick, так само, як воно зробило б його локальним у функції верхнього рівня. Ключове слово nonlocal повідомляє Python: «ця назва знаходиться у зовнішній функції, перепризначте її там»:
def make_counter():
count = 0
def tick():
nonlocal count
count += 1
return count
return tick
c = make_counter()
print(c(), c(), c())
Виведення:
1 2 3
nonlocal відноситься до області видимості зовнішньої функції так само, як global – до області видимості модуля. Зауважте, що мутування захопленого об’єкта (виклик some_list.append(...), some_dict[k] = v) не потребує nonlocal – назва не перепризначається, змінюється лише об’єкт, на який вона вказує.