Области видимости переменных и замыкания в Python: правило LEGB
Этот урок переведён с английского оригинала.Read in EnglishLeer en españolСообщить об ошибке в переводе
Представьте дом с несколькими комнатами. В каждой комнате свои вещи: лампа на тумбочке в спальне, нож в ящике на кухне. Вы можете пользоваться вещами в той комнате, где находитесь, и можете выйти в коридор, чтобы взять что-то общее. Но дотянуться до вещей в чужой закрытой комнате, не открыв дверь, не получится.
Переменные в Python устроены так же. Каждая переменная живёт в своей области видимости, то есть в определённой части кода, где эта переменная существует и доступна. Понимание областей видимости помогает писать аккуратные функции, которые случайно не перезаписывают данные друг друга.
В этом уроке вы узнаете о четырёх уровнях областей видимости в Python (правило LEGB), научитесь пользоваться ключевыми словами global и nonlocal и разберётесь, как замыкания позволяют внутренним функциям запоминать значения из родительской функции. К концу урока вы сможете уверенно разбираться даже с самыми запутанными ошибками вида «переменная не найдена».
Что такое локальная область видимости?
Переменная, созданная внутри функции, существует только внутри этой функции. Это называется локальной областью видимости. Представьте её как личную комнату: ничто за пределами функции не может её увидеть или тронуть.
Hello from inside!
Переменная message создаётся внутри greet(), поэтому она локальна для этой функции. Как только greet() завершится, message исчезнет. Если попытаться использовать её снаружи, Python вызовет исключение NameError.
Каждый вызов функции создаёт новый набор локальных переменных. Между вызовами они не сохраняются:
x is 1 x is 1 x is 1
Что такое глобальная область видимости?
Переменная, созданная вне всех функций, живёт в глобальной области видимости. Это как вещь в коридоре вашего дома: её видно из любой комнаты.
Language: Python Welcome to Python!
Обе функции могут читать глобальную переменную language. Но что произойдёт, если попытаться изменить её внутри функции?
(нет вывода)
Этот код падает с ошибкой UnboundLocalError. Python видит внутри функции counter = ... и решает, что counter является локальной переменной. Но затем правая часть пытается прочитать counter раньше, чем ей присвоено локальное значение. Python запутывается.
Правило LEGB: как Python ищет переменные
Когда вы используете имя переменной, Python ищет его в четырёх местах в определённом порядке. Это называется правилом LEGB:
print, len, range.Python проверяет их по порядку: сначала L, потом E, потом G, потом B. Поиск останавливается на первом совпадении. Если имя не нашлось нигде, вы получите NameError.
local x enclosing x global x
Каждый print(x) находит свою переменную x, потому что ищет её, начиная со своей области видимости и двигаясь наружу. Внутренняя функция сразу находит свою локальную x и дальше не ищет.
Ключевое слово global
Если нужно изменить глобальную переменную внутри функции, об этом нужно явно сказать Python с помощью ключевого слова global. Это как получить особый ключ, чтобы выйти в коридор и переставить там вещи.
Score is now 10 Score is now 35 Score is now 40 Final score: 40
Объявление global score говорит Python: «Я не создаю новую локальную переменную, я хочу использовать глобальную». Теперь присваивание score = score + points работает правильно.
total = 0
def add(n):
global total
total = total + n
add(5)
add(3)
print(total) # 8def add(current, n):
return current + n
total = 0
total = add(total, 5)
total = add(total, 3)
print(total) # 8Объемлющая область видимости и замыкания
Если определить функцию внутри другой функции, внутренняя функция получает доступ к переменным внешней. Область видимости внешней функции называется объемлющей (enclosing): это буква «E» в LEGB.
Hello, Alice! Hello, Bob! Howdy, Alice!
Здесь происходит кое-что удивительное. make_greeting("Hello") завершает работу и возвращает внутреннюю функцию greet. Обычно локальная переменная greeting была бы уничтожена. Но она всё ещё нужна внутренней функции!
Python сохраняет greeting, потому что greet на неё ссылается. Такое сочетание функции и переменных, которые она запомнила из объемлющей области видимости, называется замыканием (closure).
Замыкания удобны для создания специализированных функций. Вот практический пример, фабрика функций-умножителей:
10 15 200
Ключевое слово nonlocal
Как global позволяет изменять глобальные переменные, так nonlocal позволяет изменять переменные объемлющей области видимости. Без него присваивание создало бы новую локальную переменную.
1 2 3
Без nonlocal count строка count = count + 1 упала бы с ошибкой UnboundLocalError, той же, что мы видели с глобальными переменными. Ключевое слово nonlocal говорит Python искать переменную в ближайшей внешней области видимости.
Это мощный приём. Переменная count полностью скрыта от внешнего мира. Никакой код за пределами make_counter не может прочитать или испортить её. Это одна из форм инкапсуляции, то есть сокрытия данных.
Частые ошибки с областями видимости и как их исправить
Вот ошибки с областями видимости, на которых Python-разработчики спотыкаются чаще всего. Если знать эти шаблоны, можно сэкономить часы отладки.
Ошибка 1: затенение глобальной переменной
Inside: ['date'] Outside: ['apple', 'banana', 'cherry']
items = [] внутри функции создаёт совершенно новую локальную переменную, которая затеняет (скрывает) глобальную. Глобальный список items при этом не меняется. Обычно это ошибка.
Ошибка 2: UnboundLocalError из-за смешения чтения и записи
(нет вывода)
Python просматривает всё тело функции ещё до её запуска. Если он видит хоть одно присваивание total, он помечает total как локальную переменную для всей функции. Тогда строка print(total) пытается прочитать локальную переменную, которой ещё ничего не присвоено.
Практические упражнения
Пора применить области видимости и замыкания на практике. Упражнения идут от чтения кода с разными областями видимости к созданию собственных замыканий.
Внимательно прочитайте код и предскажите, что он выведет. Затем напишите одну инструкцию print(), которая выводит точно такой же результат.
x = 10
def foo():
x = 20
print(x)
foo()
print(x)Выведите два числа на отдельных строках, точно так, как их вывел бы код выше.
Этот код пытается увеличить глобальный счётчик, но падает с ошибкой UnboundLocalError. Исправьте его, чтобы вывод был таким:
1
2
3Напишите функцию make_greeter, которая принимает строку greeting как аргумент и возвращает новую функцию. Возвращённая функция должна принимать аргумент name и выводить приветствие с этим именем.
Например:
hello = make_greeter("Hello")
hello("World")Должно вывести: Hello, World!
Тест вызовет:
hi = make_greeter("Hi")
hi("Alice")
hi("Bob")Ожидаемый вывод:
Hi, Alice!
Hi, Bob!Напишите функцию make_counter, которая принимает начальное значение start и возвращает функцию. При каждом вызове возвращённая функция должна выводить текущее значение счётчика, а затем увеличивать его на 1.
Тест вызовет:
c = make_counter(1)
c()
c()
c()Ожидаемый вывод:
1
2
3Напишите функцию make_averager, которая возвращает функцию. При каждом вызове с числом возвращённая функция должна выводить текущее среднее всех чисел, переданных ей до сих пор.
Текущее среднее равно сумме всех чисел, делённой на их количество.
Тест вызовет:
avg = make_averager()
avg(10)
avg(20)
avg(30)Ожидаемый вывод:
10.0
15.0
20.0Подсказка: функции нужно помнить и общую сумму, и количество чисел.
Итоги
Вот что вы узнали об областях видимости в Python:
| Понятие | Что это значит |
|---|---|
| Локальная область видимости | Переменные, созданные внутри функции, существуют только там |
| Глобальная область видимости | Переменные, созданные на уровне модуля, видны везде |
| Правило LEGB | Python ищет имена в порядке Local, Enclosing, Global, Built-in |
Ключевое слово global | Позволяет функции изменять переменную уровня модуля |
| Объемлющая область видимости | Переменные внешней функции, доступные внутренним функциям |
| Замыкание | Функция, которая запоминает переменные из объемлющей области видимости |
Ключевое слово nonlocal | Позволяет внутренней функции изменять переменную объемлющей функции |
Золотое правило: лучше передавать аргументы и возвращать значения, чем использовать global или nonlocal. Исключение составляют замыкания: здесь объемлющая область видимости показывает себя во всей красе и позволяет создавать специализированные функции с собственным скрытым состоянием.
Что дальше?
Теперь, когда вы разобрались с областями видимости и замыканиями, можно переходить к уроку [Рекурсия](/python/python-recursion), где функции вызывают сами себя. Для рекурсии важно понимать области видимости, потому что каждый рекурсивный вызов получает собственную локальную область видимости.