Перейти к содержанию

412 уроков, 25 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 5 из 20 Начальный 15 мин 100 XP

Область видимости в 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)
Python ответит NameError: name 'items' is not defined. К пятой строке вызов закончился, и локальная items исчезла вместе с ним. Нужен результат — возвращайте его через return.

Читать глобальную — можно

Переменная, созданная вне всех функций, — глобальная: она видна всему файлу, включая тела функций. Функция может читать глобальное имя свободно:

функция читает глобальную переменную
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)
Python ответит UnboundLocalError: local variable 'total' referenced before assignment. Присваивание total = ... внутри функции объявило локальную total, и читать её до первой записи нельзя. Причина в правиле из предыдущего блока: присваивание всегда создаёт локальную.

Правильный путь: аргументы и 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. Он объявляет: присваивания в этой функции относятся к глобальному имени, а не создают локальное:

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встроенные имена Pythonprint, 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

Соберём вместе всё, что разобрали: глобальная константа читается, а всё изменяемое живёт в аргументах и возвращается.

сумма заказа через return
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)
Проверь себя
0 / 9

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 пишут ЗАГЛАВНЫМИ буквами?

Карточки терминов
Запомнено: 0 / 8
Практика

Дана глобальная переменная balance = 1000. Напиши функцию withdraw(amount, balance), которая возвращает баланс после списания: balance - amount, а если результат отрицательный — возвращает 0. Переприсвой глобальный balance дважды: withdraw(300, balance), потом withdraw(900, balance), и напечатай итог.

practice.py
Вопросы и ответы по уроку

Почему функция не видит переменную из другой функции?

Каждая функция — своя область видимости: её локальные переменные создаются при вызове и исчезают вместе с ним, соседние функции их не видят. Обмен идёт через аргументы и 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-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Что дальше

Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.