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