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 – имя не переприсваивается, меняется лишь объект, на который оно указывает.