Что такое пространство имён в Python и как работает правило LEGB при поиске переменных?
Часто слышу «пространство имён» (namespace) и аббревиатуру LEGB, но цельной картины нет. Пытаюсь понять, почему вот тут Python берёт именно ту x, а не другую:
x = "глобальная"
def outer():
x = "из outer"
def inner():
print(x) # какую x он напечатает и почему?
inner()
outer()
Объясните, что такое namespace и в каком порядке Python ищет имя.
2 ответа
Пространство имён (namespace) — это просто «словарь», связывающий имена с объектами. У модуля своё, у каждой функции при вызове своё, у встроенных функций своё. Когда ты пишешь x, Python должен решить, в каком namespace искать это имя. Делает он это по правилу LEGB — это порядок поиска:
- L (Local) — локальные имена текущей функции;
- E (Enclosing) — имена объемлющих функций (для вложенных функций/замыканий);
- G (Global) — имена на уровне модуля;
- B (Built-in) — встроенные (
len,print,range, ...).
Поиск идёт строго в этом порядке: нашёл на первом подходящем уровне — остановился. В твоём примере inner() ищет x: локально в inner нет → смотрит Enclosing, то есть outer, там x = "из outer" — берёт её. Напечатает из outer. До глобальной x дело не дойдёт.
Если захочешь ПИСАТЬ во внешнюю переменную, а не читать, нужны ключевые слова:
x = "глобальная"
def outer():
x = "из outer"
def inner():
nonlocal x # пишем в x из enclosing (outer)
x = "изменено inner"
inner()
print(x) # 'изменено inner'
outer()
print(x) # 'глобальная' — глобальную не трогали
global x — писать в глобальную (модульную), nonlocal x — писать в ближайшую enclosing. Без них присваивание x = ... внутри функции просто создаёт новую ЛОКАЛЬНУЮ переменную, а внешнюю не меняет.
Классическая ловушка LEGB — UnboundLocalError:
x = 10
def f():
print(x) # UnboundLocalError!
x = 5
Казалось бы, должна напечататься глобальная x = 10. Но раз внутри функции есть присваивание x = 5, Python ещё на этапе компиляции решает, что x — локальная на ВСЮ функцию. И на момент print она ещё не создана. Лечится либо global x (если правда нужна глобальная), либо переименованием. Полезно подсмотреть, что реально в namespace, через locals() и globals().