Python: LOAD_FAST против LOAD_DEREF с добавлением на месте
В прошлую пятницу я пошел на собеседование и должен был ответить на следующий вопрос: почему этот код вызывает исключение (UnboundLocalError: local variable 'var' referenced before assignment
на строке, содержащейvar += 1
)?
def outer():
var = 1
def inner():
var += 1
return var
return inner
Я не мог дать правильный ответ; этот факт очень расстроил меня, и когда я пришел домой, я очень старался найти правильный ответ. Ну яиметь нашел ответ, но теперь есть кое-что еще, что смущает меня.
Я должен сказать заранее что мой вопрос больше о решениях, принимаемых при разработке языка, а не о том, как он работает.
Итак, рассмотрим этот код. Внутренняя функция - это закрытие Python, иvar
не является местным дляouter
- хранится в ячейке (а затем извлекается из ячейки):
def outer():
var = 1
def inner():
return var
return inner
Разборка выглядит так:
0 LOAD_CONST 1 (1)
3 STORE_DEREF 0 (var) # not STORE_FAST
6 LOAD_CLOSURE 0 (var)
9 BUILD_TUPLE 1
12 LOAD_CONST 2 (<code object inner at 0x10796c810)
15 LOAD_CONST 3 ('outer.<locals>.inner')
18 MAKE_CLOSURE 0
21 STORE_FAST 0 (inner)
24 LOAD_FAST 0 (inner)
27 RETURN_VALUE
recursing into <code object inner at 0x10796c810:
0 LOAD_DEREF 0 (var) # same thing
3 RETURN_VALUE
Это меняется, когда мы пытаемся связать что-то еще сvar
внутри внутренней функции:
def outer():
var = 1
def inner():
var = 2
return var
return inner
Еще раз разборка:
0 LOAD_CONST 1 (1)
3 STORE_FAST 0 (var) # this one changed
6 LOAD_CONST 2 (<code object inner at 0x1084a1810)
9 LOAD_CONST 3 ('outer.<locals>.inner')
12 MAKE_FUNCTION 0 # AND not MAKE_CLOSURE
15 STORE_FAST 1 (inner)
18 LOAD_FAST 1 (inner)
21 RETURN_VALUE
recursing into <code object inner at 0x1084a1810:
0 LOAD_CONST 1 (2)
3 STORE_FAST 0 (var) # 'var' is supposed to be local
6 LOAD_FAST 0 (var)
9 RETURN_VALUE
Мы хранимvar
локально, что соответствует тому, что сказано в документации:присваивание имен всегда входит в самую внутреннюю область видимости.
Теперь, когда мы пытаемся сделать приращениеvar += 1
противныйLOAD_FAST
появляется, который пытается получитьvar
отinner
Локальная сфера применения:
14 LOAD_FAST 0 (var)
17 LOAD_CONST 2 (2)
20 INPLACE_ADD
21 STORE_FAST 0 (var)
И конечно мы получаем ошибку. Сейчас,вот что я не понимаю: почему мы не можем получитьvar
сLOAD_DEREF
и затем храните его внутриinner
размах сSTORE_FAST
? Я имею в виду, это, кажется, О.К. с заданием «внутренней области видимости», и в то же время это несколько более интуитивно желательно. По крайней мере+=
код будет делать то, что мы хотим, и я не могу придумать ситуацию, в которой описанный подход может что-то испортить.
Ты можешь? Я чувствую, что мне чего-то не хватает здесь.