2.16. Область видимости

Когда Python ищет имя внутри функции, он просматривает определённую последовательность областей видимости. Понимание этой последовательности объясняет, почему одни присваивания затеняют внешние имена, почему другие изменяют их и почему вложенные функции могут запоминать значения из места, где они были определены.

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.

Поиск имени начинается в локальной области видимости функции и идёт наружу к областям видимости модуля и встроенной, пока не найдётся совпадение.

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