Область видимости в Python: локальное и глобальное
Переменная, созданная внутри функции, снаружи не существует. Разбираем локальные и глобальные имена, ловушку UnboundLocalError и почему global лучше не трогать.
Редакция Питоники
Каждый урок раздела заводил переменные: items в теле функции, result в вычислениях. Возникает вопрос: где эти переменные живут и кто их видит? Ответ — область видимости: правило Python о том, какие имена доступны в какой части программы. Функция — это комната со своими переменными: то, что создано внутри, снаружи не существует. Понимание границы избавит от самой загадочной ошибки функций — UnboundLocalError.
Переменная внутри функции снаружи не видна
Создадим переменную в теле функции и попробуем дотянуться до неё снаружи:
def count():
items = 3
print('внутри функции:', items)
count()
внутри функции: 3
Внутри вызова items живёт и печатается. Но это локальная переменная: она создана в теле функции и существует только во время вызова. Попытка прочитать её после вызова провалится:
# Так не запускается: items живёт только во время вызова
def count():
items = 3
print('внутри:', items)
count()
print(items)
Читать глобальную — можно
Переменная, созданная вне всех функций, — глобальная: она видна всему файлу, включая тела функций. Функция может читать глобальное имя свободно:
rate = 90
def to_dollars(rubles):
result = rubles / rate
print(result)
to_dollars(900)
to_dollars(1800)
10.0 20.0
rate объявлена вне функции, поэтому to_dollars нашла её в глобальной области и использовала в делении. Чтение — безопасная операция: функция не меняет глобальную переменную, только смотрит. result при этом осталась локальной — она создана присваиванием внутри тела.
Присваивание создаёт локальную — даже с чужим именем
А вот запись меняет правила. Если внутри функции присваивается имя, совпадающее с глобальным, Python создаёт новую локальную переменную с тем же именем — и глобальная остаётся нетронутой:
counter = 100
def reset():
counter = 0
print('внутри:', counter)
reset()
print('снаружи:', counter)
внутри: 0 снаружи: 100
Внутри reset напечатался ноль — но это была своя, локальная counter. Глобальная counter сохранила сто. Присваивание внутри функции создаёт локальную переменную, даже если снаружи есть переменная с тем же именем. Программисты говорят «локальная затеняет глобальную»: ошибки здесь нет, но результат не тот, которого ждали.
# Так не запускается: total внутри функции — локальная, а её читают до присваивания
total = 0
def add_money(amount):
total = total + amount
add_money(100)
Правильный путь: аргументы и return
Данные не нужно таскать через глобальные переменные: аргументы доставляют внутрь, return — обратно. Тот же накопительный счётчик пишется без единого присваивания глобальному имени из тела:
def add(amount, total):
return total + amount
total = 0
total = add(100, total)
total = add(50, total)
print(total)
150
Функция add зависит только от своих аргументов: ей не важно, что снаружи есть переменная total — она берёт значение из параметра. Такой код предсказуем: сколько бы раз ни вызывали add с одними данными, ответ будет одним и тем же, а глобальные имена остаются нетронутыми.
global: выключатель, который лучше не трогать
Для полноты картины — оператор global. Он объявляет: присваивания в этой функции относятся к глобальному имени, а не создают локальное:
counter = 0
def bump():
global counter
counter = counter + 1
bump()
bump()
print(counter)
2
С global counter строка counter = counter + 1 действительно увеличила глобальную переменную: два вызова — и снаружи напечаталась двойка. Работает — но профессиональный код global почти не использует. Функция с global зависит от снаружи: её результат меняется от того, что лежит в глобальной переменной до вызова, и предсказать поведение без чтения всей программы нельзя.
Собираем: функция читает настройку, но не меняет её
Рабочая комбинация: глобальная константа-настройка для чтения и параметры с возвратом для всего остального:
price = 100
def final_price(quantity):
if quantity >= 10:
return quantity * price - quantity * 10
return quantity * price
print(final_price(3))
print(final_price(12))
300 1080
price — глобальная настройка: функция читает её в обеих ветках, но ни в одной не присваивает. quantity приходит аргументом, результат возвращается через return. Правило выбора простое: читать глобальное — можно, менять глобальное — через аргументы и return. С возвращаемыми значениями вы уже знакомы из урока про return, а о коротких правилах без имени пойдёт речь в уроке про lambda.
Как Python ищет имя
Когда внутри функции встречается имя, Python ищет его в четырёх местах по порядку и берёт первое найденное. Порядок этот называют LEGB — по первым буквам английских названий областей.
| Шаг | Где ищем | Пример |
|---|---|---|
| Local | тело текущей функции | total из строки total = total + amount |
| Enclosing | тело функции, в которую вложена наша | счётчик из внешней функции — это замыкания |
| Global | переменные уровня модуля | rate = 90, объявленная в файле |
| Built-in | встроенные имена Python | print, len, sum, range |
Отсюда полезное наблюдение: print внутри функции — это встроенная функция Python, а не ваша. Локального print нет, enclosing нет, глобального нет — нашёлся built-in. Стоит назвать свою функцию print — и она затенит встроенную во всём файле, после чего любая попытка напечатать что-нибудь в этой области упадёт с TypeError. Имена, занятые у Python, мы не трогаем.
Функция читает глобальное имя в момент вызова
Ещё один тонкий момент, который однажды стоил нам получаса: глобальное имя ищется не при объявлении функции, а когда функция выполняется. Если между def и вызовом глобальную переменную поменяли, функция увидит новое значение — хотя её тело никто не трогал.
value = 'первое значение'
def show():
return value
value = 'второе значение'
print(show())
второе значение
Функция show объявлена, когда value равнялась «первое значение», но вызвана позже — и вернула «второе». Отсюда практический вывод: глобальная переменная, которую кто-то меняет, делает поведение функций непредсказуемым. Читать глобальные константы безопасно; менять их — нет.
Вложенная функция видит имя внешней
Шаг Enclosing из таблицы стоит посмотреть отдельно. Если функцию объявили внутри другой функции, она видит имена внешней функции — не потому что те глобальные, а потому что область Enclosing стоит в поиске раньше Global.
def outer():
label = 'заказ готов'
def inner():
return label
return inner()
print(outer())
заказ готов
Никакого global тут нет, но label из тела outer функция inner видит: сработал шаг Enclosing. Этот механизм — основа замыканий: если inner вернуть наружу, она унесёт с собой label с собой. Подробнее — урок про замыкания.
Глобальные константы
Иногда глобальная переменная всё-таки нужна: курс валюты, лимит, версия формата, константа оформления. В таких случаях мы оставляем её глобальной, но превращаем в константу: имя заглавными буквами, значение одно и никогда не меняется.
DISCOUNT = 15
def with_discount(price):
return price - price * DISCOUNT // 100
print(with_discount(200))
print(with_discount(1000))
170 850
Заглавные буквы — это сигнал для человека, а не для Python: интерпретатору всё равно. Но сигнал полезный. Меняем DISCOUNT в одной строке — и все функции, которые её читают, подхватят новое значение. Правда, только если никто не присваивает ей другое значение по дороге, поэтому константу мы никогда не меняем в рантайме.
Практика: счётчик заказов без global
Соберём вместе всё, что разобрали: глобальная константа читается, а всё изменяемое живёт в аргументах и возвращается.
TAX_RATE = 20
def add_item(total, price):
return total + price
def with_tax(total):
return total - total * TAX_RATE // 100
order = 0
order = add_item(order, 350)
order = add_item(order, 190)
print('Со скидкой:', with_tax(order))
Со скидкой: 432
Ни одной строки global, ни одной глобальной переменной, которую кто-то меняет. Сумма передаётся аргументом и возвращается — порядок вызовов читается сверху вниз, и переставить шаги нельзя, не сломав расчёт. Такие функции можно вызвать из другого отчёта и получить тот же ответ: их поведение целиком описано аргументами. Переменная order в примере живёт на уровне модуля, но меняется только в вызывающем коде — внутри функций её никто не трогает, поэтому затенения не возникает.
Сначала предскажи ответ в голове — это главный навык программиста.
x = 5
def f():
x = 10
print(x)
f()
print(x)
def f():
print(y)
y = 7
f()
c = 0
def f():
global c
c = c + 1
f()
f()
print(c)
1. Видна ли переменная, созданная в теле функции, после окончания вызова?
2. Может ли функция читать глобальную переменную без специальных объявлений?
3. Что произойдёт при выполнении total = total + amount внутри функции, если total объявлена только глобально?
4. Глобально counter = 100, а в функции reset есть строка counter = 0. Что напечатает print(counter) после вызова reset()?
5. Что делает строка global counter внутри функции?
6. Как правильно делиться данными между функцией и остальным кодом?
7. Глобально value = 'первое значение'. Функция show возвращает value. Потом value заменили на 'второе значение' и вызвали show(). Что вернётся?
8. Что означает шаг Enclosing в порядке поиска имён (LEGB)?
9. Почему глобальные константы в Python пишут ЗАГЛАВНЫМИ буквами?
Дана глобальная переменная balance = 1000. Напиши функцию withdraw(amount, balance), которая возвращает баланс после списания: balance - amount, а если результат отрицательный — возвращает 0. Переприсвой глобальный balance дважды: withdraw(300, balance), потом withdraw(900, balance), и напечатай итог.
Почему функция не видит переменную из другой функции?
Каждая функция — своя область видимости: её локальные переменные создаются при вызове и исчезают вместе с ним, соседние функции их не видят. Обмен идёт через аргументы и return, а не через чужие переменные.
Урок 3: returnКак изменить глобальную переменную из функции?
Технически — объявить global имя и присваивать: переменная изменится. Так делать не рекомендуется: функция становится зависимой от внешнего состояния. Правильный путь — передавать значение аргументом и возвращать новый результат через return, присваивая его снаружи.
Что такое UnboundLocalError и откуда он берётся?
Это ошибка чтения локальной переменной до её первой записи. Возникает, когда внутри функции присваивается имя, совпадающее с глобальным: присваивание делает имя локальным, и строка total = total + amount пытается прочитать ещё пустую локальную total.
Живёт ли локальная переменная после окончания вызова?
Нет: она создаётся при вызове и уничтожается, когда функция закончилась. Единственный способ сохранить результат — вернуть его через return и присвоить переменной в вызывающем коде.
Почему моя функция вдруг перестала печатать?
Скорее всего, вы назвали свою функцию print — или присвоили имя print переменной. Встроенное имя затенилось: поиск идёт по порядку Local → Enclosing → Global → Built-in, и первое найденное выигрывает. Переименуйте функцию, и всё заработает.
Как передать в функцию значение, которое она должна изменить?
Аргументом на вход и результатом на выход: def add(amount, total): return total + amount. Глобальную переменную при этом не трогаем — присваиваем результат снаружи. Так функция остаётся предсказуемой: одни и те же аргументы всегда дают один и тот же ответ.
Что такое Enclosing и когда с ним сталкиваются?
Это область функции, в которую вложена другая функция. Шаг Enclosing стоит в порядке поиска имён LEGB между Local и Global: вложенная функция видит переменные внешней функции без всякого global. На этом механизме построены замыкания — урок 15.
Понравился урок? Сошлитесь на него
«Присваивание внутри функции создаёт локальную переменную, даже если снаружи есть переменная с тем же именем.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Функции · Урок 1
Функции в Python: первая def
Первая функция: задаём блоку кода имя через def и вызываем по скобкам. Разбираем объявление на части — имя, двоеточие, тело с отступом — и видим, зачем функция вместо копипасты.
Функции · Урок 3
Return в Python: функция возвращает результат
print показывает значение человеку, return отдаёт программе. Учимся возвращать результат, класть его в переменную и использовать в выражениях — и не путать return с print.
Функции · Урок 6
Lambda в Python: функция в одну строку
lambda — правило без имени: аргументы до двоеточия, одно выражение после. Учимся писать короткие правила и передавать их в другие функции аргументом.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
pytest · Урок 7
Область видимости фикстур: scope и autouse
Сколько раз создаётся фикстура? По умолчанию — для каждого теста, но scope="module" экономит до одного раза на файл, а autouse применяет её вовсе без указания в аргументах.
область видимости фикстур pytestpytest scope функции по умолчанию
pytest · Урок 12
monkeypatch: подмена функций и окружения в тестах
Функция ходит во внешний мир — за курсом валют, случайностью, переменными окружения — и тест теряет управление. monkeypatch подменяет внешний мир заглушкой на время теста и убирает её сам.
pytest заглушка функцииpytest мок функции
Функции · Урок 9
sorted с key в Python: сортировка по правилу
Порядок по своему правилу: слова по длине, пары по цене, участники по очкам. Разбираем sorted, параметр key, reverse и стабильность Timsort.
python функции уроки