ПрограммированиеPython разработчик

Что такое 'LEGB rule' в Python? Как она работает при поиске имени переменной? Приведите примеры ситуаций, когда неверное понимание этого правила приводит к нетривиальным багам.

Проходите собеседования с ИИ помощником Hintsage

Ответ.

LEGB rule — это правило поиска пространства имён (scoping) в Python. Аббревиатура расшифровывается как:

  • Local — локальное пространство имён (внутри функции);
  • Enclosing — пространства имён всех вложенных функций;
  • Global — пространство модулей (глобальные переменные);
  • Builtin — встроенные имена Python (len, list, и пр).

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

def outer(): x = 'enclosing' def inner(): x = 'local' print(x) inner() outer() # 'local'

Если внутри inner() убрать присваивание x, Python возьмёт x из enclosing scope (outer). Если и там переменной нет — ищет глобально, потом из встроенных.


Вопрос с подвохом.

Как изменится поведение функции, если вы забудете объявить nonlocal или global при модификации переменной внешнего уровня?

Ответ: Если попытаться изменить переменную внешнего scope-а внутри вложенной функции без объявления nonlocal (или global — если переменная в модуле), Python создаст новую локальную переменную, не изменив внешнюю.

Пример:

x = 10 def foo(): x = 20 # Это новая локальная x, глобальная не меняется foo() print(x) # 10

Чтобы изменить глобальную x:

def foo(): global x x = 20

С переменными во внешней функции — используйте nonlocal.


Примеры реальных ошибок из-за незнания тонкостей темы.


История 1

В образовательном проекте использовали вложенные функции для подсчёта числа вызовов. Разработчик писал:

def counter(): count = 0 def inc(): count += 1 return count return inc

Но возникал UnboundLocalError, потому что count внутри inc() считалось новым (создавался локальный count). Решение: объявить count nonlocal.


История 2

В web-приложении хотели в обработчике модифицировать глобально заданный кеш:

cache = {} def add_to_cache(key, value): cache[key] = value

Однако если бы внутри функции попытались полностью переписать cache, например cache = {key: value}, это создало бы новый локальный cache, не связанный с глобальным, из-за отсутствия global cache.


История 3

В большой ETL-системе встретился баг: программисты оставили переменную result без явного инициализации внутри функции, ожидая, что она возьмётся из внешнего scope. Но через несколько итераций результат начал сбиваться, т.к. внутри была запись result += value, а не result = result + value (без nonlocal/global) — и result инициализировалась заново на каждом вызове.