Области видимости в Python: LEGB, global и nonlocal — понятное руководство с примерами
Если ваш код «видит» не ту переменную или падает с UnboundLocalError, почти наверняка проблема в областях видимости. В этой статье мы разберём правило LEGB, а также ключевые слова global и nonlocal в Python — на практичных примерах, с советами и типичными ошибками новичков.
LEGB: как Python ищет переменные
LEGB — это порядок поиска имени переменной:
- Local — локальная область (внутри текущей функции)
- Enclosing — область внешних функций (для вложенных функций)
- Global — глобальная область модуля
- Builtins — встроенные имена Python (len, sum и др.)
Посмотрим на простой пример теневого перекрытия имён:
x = 'global'
def outer():
x = 'enclosing'
def inner():
x = 'local'
print(x) # local
inner()
print(x) # enclosing
outer()
print(x) # global
Python всегда берёт самое ближайшее определение по правилу LEGB.
global в Python: когда нужен и когда нет
Ключевое слово global говорит интерпретатору: «я собираюсь переназначать имя из глобальной области». Без этого Python посчитает имя локальным и может выдать UnboundLocalError.
x = 10
def bad_inc():
print('Перед инкрементом:', x) # Попытка прочитать локальное x до присваивания
x = x + 1 # UnboundLocalError: local variable 'x' referenced before assignment
return x
# bad_inc()
def good_inc():
global x
x = x + 1
return x
print(good_inc()) # 11
Важно: global нужен только при переприсваивании имени. Если вы мутируете глобальный изменяемый объект (список, словарь), global не требуется:
nums = [1, 2]
def add_three():
nums.append(3) # Мутация, работает без global
def reset_wrong():
nums = [] # Переприсваивание: создаётся локальное nums, глобальное не тронуты
def reset_ok():
global nums
nums = [] # Явно переприсваиваем глобальное имя
add_three()
print(nums) # [1, 2, 3]
reset_wrong()
print(nums) # [1, 2, 3] — не сбросился!
reset_ok()
print(nums) # []
Стоит ли использовать global?
В продакшене — редко. Глобальные изменяют состояние «изнутри», усложняя тестирование и сопровождение. Предпочтительнее передавать значения параметрами и возвращать результат:
def inc(value):
return value + 1
x = 10
x = inc(x)
Если нужно хранить состояние — инкапсулируйте его в объект:
class Counter:
def __init__(self, start=0):
self.value = start
def inc(self, step=1):
self.value += step
return self.value
c = Counter()
print(c.inc(), c.inc()) # 1 2
nonlocal: управление переменной внешней функции
nonlocal нужен, когда у нас есть вложенные функции и мы хотим менять переменную не глобального, а внешнего локального уровня (enclosing). Без nonlocal возникнет UnboundLocalError, как и с global-сценарием.
def outer():
x = 0
def inner_bad():
x += 1 # UnboundLocalError: local variable 'x' referenced before assignment
return x
# return inner_bad
# outer()()
def outer_fix():
x = 0
def inner():
nonlocal x # объявляем, что x — из enclosing-области, не локальная
x += 1
return x
return inner
counter = outer_fix()
print(counter(), counter(), counter()) # 1 2 3
Классический паттерн с nonlocal — «функция‑счётчик» (замыкание):
def make_counter(start=0, step=1):
value = start
def inc():
nonlocal value
value += step
return value
return inc
c1 = make_counter()
c2 = make_counter(100, 10)
print(c1(), c1(), c1()) # 1 2 3
print(c2(), c2()) # 110 120
Замыкания и «позднее связывание»
Частая ловушка при создании функций в цикле — все они «видят» последнее значение счётчика. Решение — зафиксировать значение аргументом по умолчанию:
funcs = []
for i in range(3):
def f(i=i): # фиксируем текущее i в значении по умолчанию
return i
funcs.append(f)
print([fn() for fn in funcs]) # [0, 1, 2]
Частые ошибки и как их избегать
- UnboundLocalError: вы присваиваете переменной внутри функции, значит она локальная; чтение до присваивания запрещено. Лечится global, nonlocal или грамотным рефакторингом (передать параметр, вернуть результат).
- Затенение встроенных имён: не называйте переменные как len, list, sum — вы потеряете доступ к встроенной функции.
len = 10
# print(len([1, 2])) # TypeError: 'int' object is not callable
del len # вернули доступ к встроенной len
print(len([1, 2])) # 2
- Глобальные «магические» константы: если нужны действительно глобальные значения — делайте их КОНСТАНТАМИ В ВЕРХНЕМ РЕГИСТРЕ и не меняйте (например,
API_URL). - Сложность тестирования: функции с побочными эффектами и глобальным состоянием сложнее мокать и покрывать тестами. Делайте зависимости явными (DI через параметры).
Чеклист лучших практик
- Избегайте global — предпочитайте параметры и возврат значений.
- Для состояния используйте объекты (классы) или замыкания с nonlocal.
- Не затеняйте встроенные имена и не переиспользуйте «говорящие» идентификаторы.
- Пишите маленькие, чистые функции без скрытых зависимостей — их легко тестировать и переиспользовать.
- Если всё же нужен глобальный реестр или кеш — инкапсулируйте доступ через функции/классы, а не меняйте переменные напрямую.
Итоги
Понимание LEGB и грамотное использование global/nonlocal избавят от трудноуловимых багов и сделают код чище. Начните с отказа от неявных глобальных состояний, а затем применяйте замыкания или объекты там, где нужен контролируемый стейт.
Хотите быстро и системно прокачать Python с практикой и домашками? Посмотрите программу и первые уроки: Открыть программу курса «Программирование на Python с Нуля до Гуру» — отличный путь от базовых тем до уверенного уровня.
-
Создано 12.06.2026 17:01:33
-
Михаил Русаков

Комментарии (0):
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.