NameError и UnboundLocalError в Python: области видимости LEGB и nonlocal

Python Автор: Среда и версия: CPython 3.14.5
содержание

NameError: name 'total' is not defined означает, что Python не нашёл имя total ни в одной доступной области видимости. UnboundLocalError: cannot access local variable 'total' where it is not associated with a value точнее: имя уже считается локальным в функции, но до чтения не получило значения.

UnboundLocalError — подкласс NameError, поэтому обработчик except NameError поймает обе ошибки. Для диагностики это разные сигналы: при NameError ищите опечатку, порядок выполнения или неверный импорт; при UnboundLocalError — присваивание внутри текущей функции. Примеры ниже проверены на CPython 3.14.5.

Python / 01

Имя ищется от ближайшей области к дальней

Имя ищется от ближайшей области к дальней01 L / Local rate не найдено Проверяем текущую функцию. 02 E / Enclosing rate = 0.2 → найдено Поиск заканчивается на первом совпадении во внешней функции. 03 G → B / если не нашли Global → Builtins До модуля и встроенных имён в этом примере не дошли.01L / Localrate не найденоПроверяем текущую функцию.02E / Enclosingrate = 0.2 → найденоПоиск заканчивается на первом совпадении во внешней функции.03G → B / если не нашлиGlobal → BuiltinsДо модуля и встроенных имён в этом примере не дошли.
LEGB задаёт порядок поиска имени. Python берёт первое найденное связывание; если его нет на всех четырёх уровнях, возникает NameError.

Чем NameError отличается от UnboundLocalError

СообщениеЧто уже известноЧастая причинаПервая проверка
NameError: name 'user_name' is not definedИмя не найдено по LEGBОпечатка, регистр, ранний вызов, неверная форма импортаСравнить имя символ в символ и найти, где оно должно связываться
UnboundLocalError: cannot access local variable 'discount' where it is not associated with a valueКомпилятор уже отнёс discount к локальным именам функцииПрисваивание есть ниже по коду или в невыполненной веткеНайти все discount = ..., discount += ..., цели for, with, except и импорты внутри этой функции

Проверить иерархию можно без догадок:

print(issubclass(UnboundLocalError, NameError))
True

Как Python ищет имя по LEGB

Мнемоника LEGB описывает порядок для обычного чтения имени в функции:

  1. Local — локальная область текущей функции: её параметры и имена, связанные в теле.
  2. Enclosing — локальные области объемлющих функций, от ближайшей к дальней.
  3. Global — пространство имён модуля.
  4. Builtins — встроенные имена вроде len, print и NameError.
rate = 0.1

def make_price():
    rate = 0.2

    def price(net):
        return net * (1 + rate)

    return price

price = make_price()
print(price(1000))
1200.0

rate нет в price, но он найден в ближайшей объемлющей функции make_price. Поиск не доходит до глобального rate = 0.1.

if, for, while, try и with не создают отдельных локальных областей. Имя, связанное в ветке if, остаётся локальным именем всей функции. Но значение у него появится, только если само присваивание выполнилось.

Почему присваивание делает имя локальным во всей функции

Компилятор просматривает весь блок функции до её запуска. Если в блоке есть операция связывания имени и нет global или nonlocal, все употребления этого имени в блоке считаются локальными. Неважно, стоит присваивание до чтения, после него или в ветке, которая не выполнилась.

Здесь discount = 0.2 делает discount локальным даже при vip=False:

discount = 0.1

def final_price(total, vip):
    if vip:
        discount = 0.2
    return total * (1 - discount)

final_price(1000, False)
UnboundLocalError: cannot access local variable 'discount' where it is not associated with a value

Значение должно появиться на всех путях. Здесь достаточно вычислить локальное значение один раз:

def final_price(total, vip):
    discount = 0.2 if vip else 0.1
    return total * (1 - discount)

print(final_price(1000, False))
print(final_price(1000, True))
900.0
800.0

Чтение выше присваивания падает по той же причине:

total = 0

def add_order():
    print(total)
    total += 1

add_order()

total += 1 сначала читает total, затем записывает новое значение. Сама запись делает имя локальным, поэтому первый print(total) уже не может взять глобальный total.

Опечатка, регистр, порядок определения и import

Python различает регистр: user_name, User_Name и USER_NAME — три разных имени. При опечатке CPython иногда добавляет подсказку Did you mean, но наличие и выбор кандидата не гарантированы. Сверяйте имя с местом определения.

Операторы def, class и import связывают имена только при выполнении соответствующей строки. Функции не «поднимаются» в начало файла:

print(format_name("Ada"))

def format_name(name):
    return name.upper()

Перенесите вызов после def или вызывайте его из main(), запущенной в конце файла.

Форма импорта тоже определяет, какое имя появится:

import math

print(math.sqrt(81))
9.0

После import math доступно имя math, но не sqrt. Для вызова sqrt(81) нужен from math import sqrt. Если Python не нашёл сам модуль, это уже ModuleNotFoundError, а не NameError.

Замыкания и nonlocal

Замыкание — функция, которая сохраняет доступ к именам объемлющей функции даже после её завершения. Читать такое имя можно без nonlocal. Директива нужна, когда внутренняя функция должна перепривязать имя, а не просто изменить объект через ссылку.

Без nonlocal count строка count += 1 сделает count локальным именем next_value и сразу попытается прочитать его до первого значения. С nonlocal перепривязывается count из ближайшей внешней функции:

def make_counter():
    count = 0

    def next_value():
        nonlocal count
        count += 1
        return count

    return next_value

counter = make_counter()
print(counter())
print(counter())
1
2

nonlocal ищет уже связанное имя только в объемлющих функциях. Он не может создать новое имя или ссылаться на глобальное. Если подходящего связывания нет, код не скомпилируется: SyntaxError: no binding for nonlocal 'count' found.

Замыкания часто встречаются в декораторах: обёртка помнит параметры и локальные имена функции-фабрики.

Когда global уместен, а когда маскирует проблему

global total направляет все обращения к total в текущем блоке в пространство имён модуля. Для обычного чтения глобального имени директива не нужна; она нужна для перепривязки:

total = 0

def add_order():
    global total
    total += 1

add_order()
print(total)
1

Это технически исправляет UnboundLocalError, но завязывает функцию на скрытое изменяемое состояние. Два теста или два одновременных вызова могут влиять друг на друга. Обычно проще передать значение параметром и вернуть новое:

def add_order(total):
    return total + 1

total = add_order(0)
print(total)
1

global уместен для осознанного модульного состояния. nonlocal уместен для замкнутого состояния небольшой функции. Оба слова работают на весь текущий блок и должны стоять до первого использования имени. Ни одно из них не стоит добавлять как универсальный фикс к любому UnboundLocalError.

Почему class body не обычный enclosing scope

При выполнении class Python создаёт отдельное пространство имён. Присваивания в теле класса попадают в него, а после завершения становятся атрибутами класса. Но это пространство не становится обычным Enclosing-уровнем для методов.

class Invoice:
    tax_rate = 0.2

    def total(self, net):
        return net * (1 + tax_rate)

Invoice().total(1000)

Метод не найдёт tax_rate и получит NameError. Обращайтесь к атрибуту явно:

class Invoice:
    tax_rate = 0.2

    def total(self, net):
        return net * (1 + self.tax_rate)

print(Invoice().total(1000))
1200.0

self.tax_rate подходит, если экземпляру разрешено переопределить атрибут. Если нужно значение именно текущего класса, пишите type(self).tax_rate; для конкретного класса — Invoice.tax_rate.

Область видимости comprehension в Python 3

Целевая переменная list, set или dict comprehension живёт в отдельной неявной области. В Python 3 она не «протекает» наружу:

squares = [number**2 for number in range(3)]
print(squares)
print(number)
[0, 1, 4]
NameError: name 'number' is not defined

Это защита от случайной перезаписи внешнего имени, а не ошибка comprehension. Если number нужен после, возьмите его из исходных данных или верните нужное значение явно. Синтаксис и порядок вычисления разобраны в статье про list comprehension.

Переменная исключения удаляется после except

Имя из except ... as exc доступно внутри обработчика, но Python явно очищает его при выходе из блока. Это разрывает цикл ссылок между исключением, traceback и кадром функции.

try:
    int("not a number")
except ValueError as exc:
    print(type(exc).__name__)

print(exc)

Первый print выведет ValueError, второй получит NameError: name 'exc' is not defined. Если текст нужен после обработчика, скопируйте его в другое имя:

error_message = None

try:
    int("not a number")
except ValueError as exc:
    error_message = str(exc)

print(error_message)
invalid literal for int() with base 10: 'not a number'

Общая схема try, нескольких обработчиков и finally есть в руководстве по try/except.

Безопасные исправления

Исправляйте не последнюю строку traceback, а поток данных, из-за которого у имени не оказалось значения.

  • Параметр явно передаёт зависимость в функцию. Это обычно лучше скрытого глобального имени.
  • return возвращает новое значение вместо неявной перезаписи внешнего имени.
  • Инициализация до ветвления гарантирует, что локальное имя связано на каждом пути. Запасное значение должно иметь доменный смысл, а не просто прятать ошибку.
  • Объект состояния хранит несколько связанных полей и методов. Он уместен, когда одного скаляра и return уже мало.

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

class OrderStats:
    def __init__(self):
        self.accepted = 0
        self.rejected = 0

    def record(self, is_valid):
        if is_valid:
            self.accepted += 1
        else:
            self.rejected += 1

stats = OrderStats()
stats.record(True)
stats.record(False)
print(stats.accepted, stats.rejected)
1 1

Короткий порядок диагностики

  1. Прочитайте последнюю строку traceback и выпишите точное имя.
  2. Различите NameError и UnboundLocalError; во втором случае сразу ищите связывания во всей функции.
  3. Для NameError пройдите LEGB: текущая функция, объемлющие функции, модуль, встроенные имена.
  4. Проверьте регистр, порядок def и import, а также форму импорта.
  5. Проверьте все пути управления: ветка могла не выполниться, цикл — не сделать ни одной итерации, а обработчик — не запуститься.
  6. Выберите исправление, которое делает поток данных явным: параметр и return, инициализация до ветвления, замыкание с nonlocal или объект состояния.

global и nonlocal не «объявляют» любую переменную в нужном месте. Они меняют, как компилятор связывает имя: с модулем или с ближайшей объемлющей функцией. Если функция просто получает значение и возвращает результат, ей не нужна ни одна из этих директив.

Источники