Как в реальном коде может произойти "NameError: свободная переменная 'var', на которую ссылаются перед присваиванием в пределах объема"?
Пока я болтался в чате Python, кто-то зашел и сообщил следующее исключение:
NameError: free variable 'var' referenced before assignment in enclosing scope
Я никогда раньше не видел такого сообщения об ошибке, и пользователь предоставил только небольшой фрагмент кода, который сам по себе не мог вызвать ошибку, так что я пошел искать информацию, и... кажется, не так много, Пока я искал, пользователь сообщил, что его проблема решена как "проблема с пробелами", и затем вышел из комнаты.
Немного поиграв, я смог воспроизвести исключение только с помощью игрушечного кода:
def multiplier(n):
def multiply(x):
return x * n
del n
return multiply
Что дает мне:
>>> triple = multiplier(3)
>>> triple(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in multiply
NameError: free variable 'n' referenced before assignment in enclosing scope
Все хорошо, но мне трудно понять, как это исключение может произойти в дикой природе, учитывая, что мой пример выше
- Довольно глупо
- Вряд ли случится случайно
... но, очевидно, так и есть, учитывая отчет, который я упомянул в начале этого вопроса.
Итак, как это конкретное исключение может возникнуть в реальном коде?
3 ответа
Подумайте о более сложной функции, где n
связано в зависимости от какого-либо условия, или нет. Вам не нужно del
имя, о котором идет речь, также происходит, если компилятор видит присваивание, поэтому имя является локальным, но путь к коду не берется, а имя никогда не назначается. Еще один глупый пример:
def f():
def g(x):
return x * n
if False:
n = 10
return g
Слишком поздно, чтобы ответить, но я думаю, что могу дать некоторую подробную информацию об этой ситуации. Это поможет будущим читателям увидеть, что здесь происходит.
Итак, в сообщении об ошибке говорится:
NameError: ссылка на свободную переменную 'var' перед назначением в охватывающей области
Когда мы говорим о свободных переменных, мы имеем дело с вложенными функциями. Python проделал некоторую «магию», чтобы дать вложенным функциям возможность доступа к переменным, определенным внутри их родительской области. Если мы имеем:
def outer():
foo = 10
def inner():
print(foo)
return inner
outer()() # 10
Обычно у нас не должно быть доступа кfoo
вinner
функция. Почему ? потому что после вызова и выполнения тела функции ее пространство имен уничтожается. По сути, любая локальная переменная, определенная внутри функции, становится недоступной после завершения функции.
Но у нас есть доступ...
Это волшебство происходит с помощью " Cell object ":
Объекты «Cell» используются для реализации переменных, на которые ссылаются несколько областей. Для каждой такой переменной создается объект ячейки для хранения значения; локальные переменные каждого кадра стека, который ссылается на значение, содержат ссылку на ячейки из внешних областей, которые также используют эту переменную. При доступе к значению значение, содержащееся в ячейке, используется вместо самого объекта ячейки.
Просто чтобы увидеть это скрытое сохраненное значение в ячейках (мы поговорим об этом чуть позже):
def outer():
foo = 10
def inner():
print(foo)
return inner
print(outer().__closure__[0].cell_contents) # 10
Как это работает?
Во время "компиляции "
когда Python видит функцию внутри другой функции, он принимает к сведению имена переменных, на которые ссылаются внутри вложенной функции, которые фактически определены во внешней функции. Эта информация хранится в объектах кода обеих функций. для внешней функции иco_freevars
для внутренней функции:
def outer():
foo = 10
def inner():
print(foo)
return inner
print(outer.__code__.co_cellvars) # ('foo',)
print(outer().__code__.co_freevars) # ('foo',)
Теперь время выполнения..., (см. код)
Когда Python хочет выполнить функцию, он создает «объект ячейки» для каждой переменной (co_cellvars
), который он принял к сведению.
Затем, когда он проходит по строкам, всякий раз, когда он видит присвоение таким переменным, он заполняет соответствующий объект ячейки этой переменной. (помните, что «они» косвенно содержат фактические значения.)
Когда выполнение доходит до строки создания внутренней функции, Python берет все созданные объекты ячеек и составляет из них кортеж. Затем этот кортеж присваивается внутренней функции__closure__
.
Дело в том, что при создании этого кортежа некоторые ячейки могут еще не иметь значения. Они пусты (см. вывод)!...
В этот момент, когда вы вызываете внутреннюю функцию, эти ячейки без значения вызовут указанную ошибку!
def outer():
foo = 10
def inner():
print(foo)
try:
print(boo)
except NameError as e:
print(e)
# Take a look at inner's __closure__ cells
print(inner.__closure__)
# So one boo is empty! This raises error
inner()
# Now lets look at inner's __closure__ cells one more time (they're filled now)
boo = 20
print(inner.__closure__)
# This works fine now
inner()
outer()
вывод из Python 3.10:
(<cell at 0x7f14a5b62710: empty>, <cell at 0x7f14a5b62830: int object at 0x7f14a6f00210>)
10
free variable 'boo' referenced before assignment in enclosing scope
(<cell at 0x7f14a5b62710: int object at 0x7f14a6f00350>, <cell at 0x7f14a5b62830: int object at 0x7f14a6f00210>)
10
20
Ошибкаfree variable 'boo' referenced before assignment in enclosing scope
теперь имеет смысл.
Примечание. Эта ошибка изменена в Python 3.11 на:
cannot access free variable 'boo' where it is not associated with a value in enclosing scope
Но идея та же.
Если вы посмотрите на байт-кодouter
функции, вы увидите шаги, которые я упомянул в разделе «время выполнения» в действии:
from dis import dis
def outer():
foo = 10
def inner():
print(foo)
print(boo)
boo = 20
return inner
dis(outer)
вывод из Python 3.11:
0 MAKE_CELL 1 (boo)
2 MAKE_CELL 2 (foo)
3 4 RESUME 0
4 6 LOAD_CONST 1 (10)
8 STORE_DEREF 2 (foo)
5 10 LOAD_CLOSURE 1 (boo)
12 LOAD_CLOSURE 2 (foo)
14 BUILD_TUPLE 2
16 LOAD_CONST 2 (<code object inner at 0x7fb6d4731a30, file "", line 5>)
18 MAKE_FUNCTION 8 (closure)
20 STORE_FAST 0 (inner)
8 22 LOAD_CONST 3 (20)
24 STORE_DEREF 1 (boo)
9 26 LOAD_FAST 0 (inner)
28 RETURN_VALUE
MAKE_CELL
является новым в Python3.11.
STORE_DEREF
сохраняет значение внутри объекта ячейки.
Я только что получил это сообщение об ошибке во время работы над приложением Flask.
Я пытался исправить ошибку в базе данных. У меня было много кода для настройки приложения в
Это не сработало, поэтому я переместил
Я исправил ошибку, переместив создание