Что делает global и как работают глобальные и локальные переменные?
Не понимаю, почему функция не меняет переменную снаружи:
counter = 0
def inc():
counter = counter + 1 # UnboundLocalError
inc()
Где-то советуют написать global counter. А ещё в проекте хотят «глобальную переменную, видимую из всех модулей» — как это сделать правильно?
2 ответа
Ошибка из-за правила, по которому Python ищет имена — LEGB: Local → Enclosing → Global → Built-in. Сначала локальная область функции, потом объемлющая, потом модульная (глобальная), потом встроенные имена.
Ключевой момент: если внутри функции есть присваивание имени, Python на этапе компиляции помечает имя как локальное для всей функции. В твоём counter = counter + 1 слева присваивание, значит counter локальная. А справа её читают ДО того, как присвоили — отсюда UnboundLocalError.
global говорит «работай с переменной модуля, не создавай локальную»:
counter = 0
def inc():
global counter
counter += 1
inc()
print(counter) # 1
nonlocal — то же, но для объемлющей функции (замыкания), не для модуля:
def make_counter():
n = 0
def step():
nonlocal n
n += 1
return n
return step
c = make_counter()
print(c(), c()) # 1 2
Теперь про «глобалки между модулями». Это антипаттерн. Если в a.py написать from config import x, ты копируешь ССЫЛКУ на текущее значение. Переприсвоишь x в config — у тебя в a.py останется старое, потому что это уже отдельное имя.
Как правильно: импортируй модуль целиком и обращайся через него, либо явно передавай состояние.
# config.py
state = {'counter': 0}
# worker.py
import config
def bump():
config.state['counter'] += 1 # все видят актуальное значение
Общее правило: global хорош для счётчиков-флагов в одном файле, но если состояние нужно многим — лучше передавать его аргументом или хранить в объекте/классе. Скрытые глобалки делают код непредсказуемым при тестах.
Маленькая, но спасительная деталь: global нужен только когда ты ПРИСВАИВАЕШЬ глобальной переменной. Читать и даже мутировать её можно без global:
items = []
def add(x):
items.append(x) # мутация — global НЕ нужен
add(1)
print(items) # [1]
total = 0
def reset():
total = 0 # это локальная total, снаружи 0 не изменится!
Именно из-за этой разницы между «мутирую объект» и «переприсваиваю имя» новички путаются. append/+= у списка меняют сам объект, а total = 0 создаёт новое локальное имя.