Области видимости: LEGB, global, nonlocal
Правило LEGB
Когда Python встречает имя, он ищет его в четырёх областях по порядку:
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
Что выведет код?
x = 10
def f():
print(x)
x = 20
f()
Допишите функцию 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, "Счётчики должны быть независимыми"
def make_counter():
count = 0
def inc():
nonlocal count
count += 1
return count
return inc
В каком порядке Python ищет имя внутри вложенной функции?
- Enclosing — имена объемлющих функций (снаружи внутрь не ищет)
- Local — локальные имена текущей функции
- Built-in — встроенные имена (len, print, Exception…)
- Global — имена модуля
Что выведет код?
def outer():
x = "outer"
def inner():
x = "inner"
inner()
return x
print(outer())
Какие утверждения верны?
Войдите, чтобы отмечать пройденные уроки.