PythonJuniorTechnical

Объясни LEGB-правило поиска имён.

LEGB — порядок поиска имени: Local → Enclosing → Global → Builtins. Область определяется на этапе компиляции: любое присваивание в функции делает имя локальным на всю функцию, а для записи во внешние области нужны global и nonlocal.

Что такое LEGB

LEGB — порядок, в котором интерпретатор Python ищет значение имени при его чтении: Local (тело текущей функции), Enclosing (объемлющие функции, для замыканий), Global (модуль) и Builtins (модуль builtins: len, print, list и т.д.). Первое совпадение и определяет, чему равно имя.

Как Python определяет область

Область вычисляется во время компиляции функции, а не в рантайме. Правило простое: если в теле функции имя где-либо присваивается (через =, for x in ..., except ... as x, with ... as x, аргумент функции), оно считается локальным на всю функцию. Поэтому чтение такой переменной до присваивания даёт UnboundLocalError, а не падение в Global.

Чтобы писать во внешние области используют global (модульный уровень) и nonlocal (ближайшая объемлющая функция, не модуль).

Пример всех четырёх уровней

x = "global"  # G

def outer():
    x = "enclosing"  # E

    def inner():
        x = "local"  # L
        return x

    return inner(), x

print(outer())      # ('local', 'enclosing')
print(x)            # global
print(len([1, 2]))  # B — len берётся из builtins

Демонстрация UnboundLocalError и nonlocal

counter = 0

def bad():
    # counter присваивается ниже => он локальный на всю функцию
    print(counter)   # UnboundLocalError
    counter = 1

def make_counter():
    n = 0
    def inc():
        nonlocal n   # без nonlocal n стало бы локальным в inc
        n += 1
        return n
    return inc

Подводные камни

  • Присваивание внутри функции делает имя локальным целиком, даже если используется выше по тексту — отсюда UnboundLocalError.
  • global не работает для вложенной функции; для перезаписи в объемлющей нужен nonlocal.
  • Затенение builtins (list = [...], id = ..., type = ...) ломает код модуля молча.
  • Класс не создаёт область enclosing для методов: метод не видит имена тела класса напрямую, только через self или ClassName.attr.
  • Comprehensions (кроме объектного [x for x in ...] в Python 2) имеют собственную область — переменная цикла не утекает наружу в Python 3.

Common mistakes

  • Путать enclosing и global.
  • Не объяснять UnboundLocalError.
  • Забывать builtins как последний уровень.

What the interviewer is testing

  • Называет все четыре уровня.
  • Объясняет влияние присваивания.
  • Может предсказать поиск имени во вложенной функции.

Sources

Related topics