Области видимости: LEGB, global, nonlocal

⏱ 25 мин · 🧩 заданий: 5

Правило LEGB

Когда Python встречает имя, он ищет его в четырёх областях по порядку:

┌───────────────────────────────────────────────┐ │ B Built-in: print, len, int, Exception ... │ │ ┌─────────────────────────────────────────┐ │ │ │ G Global: имена модуля │ │ │ │ ┌───────────────────────────────────┐ │ │ │ │ │ E Enclosing: объемлющая функция │ │ │ │ │ │ ┌─────────────────────────────┐ │ │ │ │ │ │ │ L Local: текущая функция │ │ │ │ │ │ │ └─────────────────────────────┘ │ │ │ │ │ └───────────────────────────────────┘ │ │ │ └─────────────────────────────────────────┘ │ └───────────────────────────────────────────────┘ поиск: L → E → G → B, первое найденное
x = "global"

def outer():
    x = "enclosing"
    def inner():
        x = "local"
        print("inner:", x)
    inner()
    print("outer:", x)

outer()
print("module:", x)
print(len)          # built-in

Пройдите пример 👣 Пошагово: у каждого вызова — свой кадр со своим x.

Присваивание делает имя локальным

Ключевое правило: если в функции есть присваивание имени (где угодно в теле), это имя локально во всей функции. Решение принимает компилятор заранее, а не во время выполнения.

counter = 0

def increment():
    counter += 1        # это присваивание → counter локальная → читается до присваивания
    return counter

increment()             # UnboundLocalError

То же самое с for x in ..., import x, def x, with ... as x, except E as x — всё это присваивания.

global

counter = 0

def increment():
    global counter      # имя counter — глобальное
    counter += 1

increment()
increment()
print(counter)

Глобальное состояние — плохая практика

Функции, меняющие глобальные переменные, трудно тестировать и понимать: результат зависит от того, что и когда вызывалось раньше. Передавайте данные через параметры и return, а состояние храните в объектах.

nonlocal

Для переменной объемлющей функции — nonlocal:

def make_accumulator():
    total = 0
    def add(x):
        nonlocal total
        total += x
        return total
    return add

acc = make_accumulator()
print(acc(10), acc(5), acc(100))

add «помнит» переменную total даже после того, как make_accumulator завершилась — это замыкание (следующий урок).

Изменение объекта — не присваивание

items = []

def add_item(x):
    items.append(x)     # мутация объекта: имя items не перепривязывается → global не нужен

add_item(1)
add_item(2)
print(items)

Разница принципиальная: items.append(x) меняет объект, а items = [...] перепривязывает имя.

Затенение встроенных имён

def show():
    max = 5                    # локальная max затеняет встроенную
    return max

print(show())
print(max(3, 7))               # снаружи встроенная max не пострадала

import builtins
print(builtins.max is max)

Блоки не создают область видимости

for i in range(3):
    last = i * 10
print(i, last)       # переменные цикла видны после цикла

if True:
    inside_if = "видно"
print(inside_if)

squares = [n * n for n in range(3)]
# print(n)          # а вот у включений своя область — n снаружи не видна

Область видимости класса

Тело класса — тоже область, но методы её не видят напрямую (нужно self. или ИмяКласса.):

class Config:
    prefix = "app_"
    names = [prefix + s for s in ("a", "b")]   # NameError: включение не видит prefix класса

Это частая неожиданность: включение внутри тела класса — отдельная функция, и область класса в цепочку LEGB не входит.

Итог

  • Поиск имён: Local → Enclosing → Global → Built-in.
  • Любое присваивание в функции делает имя локальным во всей функции → UnboundLocalError.
  • global — присвоить глобальной, nonlocal — переменной объемлющей функции.
  • Мутация объекта не требует global/nonlocal.
  • if/for не создают область; включения и функции — создают; область класса не видна из методов и включений.

Практика решено 0 из 5

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Один вариант ответа

Что выведет код?

x = 10

def f():
    print(x)
    x = 20

f()
Задание 2 Программа на Python

Допишите функцию make_counter(): она возвращает функцию inc(), которая при каждом вызове увеличивает свой счётчик на 1 и возвращает новое значение. Используйте nonlocal.

Какие тесты проверяют код (2)
c = make_counter(); assert [c(), c(), c()] == [1, 2, 3]
a = make_counter(); b = make_counter(); a(); a(); assert b() == 1, "Счётчики должны быть независимыми"
Задание 3 Расставить по порядку

В каком порядке Python ищет имя внутри вложенной функции?

  1. Enclosing — имена объемлющих функций (снаружи внутрь не ищет)
  2. Local — локальные имена текущей функции
  3. Built-in — встроенные имена (len, print, Exception…)
  4. Global — имена модуля
Задание 4 Один вариант ответа

Что выведет код?

def outer():
    x = "outer"
    def inner():
        x = "inner"
    inner()
    return x

print(outer())
Задание 5 Несколько вариантов

Какие утверждения верны?

Войдите, чтобы отмечать пройденные уроки.