listcomp не может получить доступ к локальным элементам, определенным в коде, вызываемом exec, если вложен в функцию
Существуют ли какие-либо гуру Python, способные объяснить, почему этот код не работает:
def f(code_str):
exec(code_str)
code = """
g = 5
x = [g for i in range(5)]
"""
f(code)
Ошибка:
Traceback (most recent call last):
File "py_exec_test.py", line 9, in <module>
f(code)
File "py_exec_test.py", line 2, in f
exec(code_str)
File "<string>", line 3, in <module>
File "<string>", line 3, in <listcomp>
NameError: name 'g' is not defined
пока этот работает нормально:
code = """
g = 5
x = [g for i in range(5)]
"""
exec(code)
Я знаю, что это как-то связано с локальными и глобальными переменными, как будто я передаю функцию exec локальным и глобальным переменным из моей основной области видимости, все работает нормально, но я не совсем понимаю, что происходит.
Может ли это быть ошибка с Cython?
РЕДАКТИРОВАТЬ: пробовал это с Python 3.4.0 и Python 3.4.3
3 ответа
Проблема заключается в том, что понимание списка в exec()
,
Когда вы делаете функцию (в данном случае понимание списка) вне exec()
, синтаксический анализатор создает кортеж со свободными переменными (переменные, используемые блоком кода, но не определенные им, т.е. g
в твоем случае). Этот кортеж называется закрытием функции. Хранится в __closure__
член функции.
Когда в exec()
синтаксический анализатор не будет создавать замыкание в понимании списка и вместо этого попытается по умолчанию заглянуть в globals()
толковый словарь. Вот почему добавление global g
в начале кода будет работать (а также globals().update(locals())
).
С использованием exec()
в двухпараметрической версии также решит проблему: Python объединит словарь globals() и locals() в один (согласно документации). Когда присваивание выполняется, оно выполняется в глобальных и локальных сетях одновременно. Так как Python проверит глобалы, этот подход будет работать.
Вот еще один взгляд на проблему:
import dis
code = """
g = 5
x = [g for i in range(5)]
"""
a = compile(code, '<test_module>', 'exec')
dis.dis(a)
print("###")
dis.dis(a.co_consts[1])
Этот код производит этот байт-код:
2 0 LOAD_CONST 0 (5)
3 STORE_NAME 0 (g)
3 6 LOAD_CONST 1 (<code object <listcomp> at 0x7fb1b22ceb70, file "<boum>", line 3>)
9 LOAD_CONST 2 ('<listcomp>')
12 MAKE_FUNCTION 0
15 LOAD_NAME 1 (range)
18 LOAD_CONST 0 (5)
21 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
24 GET_ITER
25 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
28 STORE_NAME 2 (x)
31 LOAD_CONST 3 (None)
34 RETURN_VALUE
###
3 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_GLOBAL 0 (g) <---- THIS LINE
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
Обратите внимание, как он выполняет LOAD_GLOBAL
загрузить g
в конце.
Теперь, если у вас есть этот код:
def Foo():
a = compile(code, '<boum>', 'exec')
dis.dis(a)
print("###")
dis.dis(a.co_consts[1])
exec(code)
Foo()
Это обеспечит точно такой же байт-код, что проблематично: так как мы находимся в функции, g
не будет объявлено в глобальной переменной, но в локальных функциях. Но Python пытается найти его в глобальных переменных (с LOAD_GLOBAL
)!
Это то, что переводчик делает за пределами exec()
:
def Bar():
g = 5
x = [g for i in range(5)]
dis.dis(Bar)
print("###")
dis.dis(Bar.__code__.co_consts[2])
Этот код дает нам этот байт-код:
30 0 LOAD_CONST 1 (5)
3 STORE_DEREF 0 (g)
31 6 LOAD_CLOSURE 0 (g)
9 BUILD_TUPLE 1
12 LOAD_CONST 2 (<code object <listcomp> at 0x7fb1b22ae030, file "test.py", line 31>)
15 LOAD_CONST 3 ('Bar.<locals>.<listcomp>')
18 MAKE_CLOSURE 0
21 LOAD_GLOBAL 0 (range)
24 LOAD_CONST 1 (5)
27 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
30 GET_ITER
31 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
34 STORE_FAST 0 (x)
37 LOAD_CONST 0 (None)
40 RETURN_VALUE
###
31 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_DEREF 0 (g) <---- THIS LINE
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
Как вы видете, g
загружается с помощью LOAD_DEREF
, доступно в кортеже, созданном в BUILD_TUPLE
, который загрузил переменную g
с помощью LOAD_CLOSURE
, MAKE_CLOSURE
оператор создает функцию, так же, как MAKE_FUNCTION
видел раньше, но с закрытием.
Вот мое предположение о том, почему это так: замыкания создаются при необходимости, когда модуль читается в первый раз. когда exec()
выполняется, он не может реализовать функции, определенные в исполняемом коде, и требует закрытия. Для него код в его строке, который не начинается с отступа, находится в глобальной области видимости. Единственный способ узнать, был ли он вызван способом, который требует закрытия, потребовал бы exec()
проверить текущую сферу (что мне кажется довольно хакерским).
Это действительно неясное поведение, которое можно объяснить, но, безусловно, вызывает удивление, когда это происходит. Это побочный эффект, хорошо объясненный в руководстве по Python, хотя трудно понять, почему он применим к данному конкретному случаю.
Весь мой анализ был сделан на Python 3, я ничего не пробовал на Python 2.
РЕДАКТИРОВАТЬ 2
Как заметили другие комментаторы, вы, похоже, обнаружили ошибку в Python 3 (для меня это не произошло в 2.7).
Как указано в комментариях ниже этого ответа, оригинальный код:
def f(code_str):
exec(code_str)
функционально эквивалентно:
def f(code_str):
exec(code_str, globals(), locals())
На моей машине, работающей 3.4, она функционально эквивалентна тому, что она взорвется точно так же. Ошибка здесь связана с запуском понимания списка при наличии двух отображаемых объектов. Например:
def f(code_str):
exec(code_str, globals(), {})
также потерпит неудачу с тем же исключением.
Чтобы не спровоцировать эту ошибку, вы должны передать ровно один объект сопоставления (поскольку отсутствие передачи равнозначно передаче двух), и чтобы гарантировать, что он работает во всех случаях, вы никогда не должны передавать функцию locals()
как этот объект отображения.
Остальная часть этого ответа была написана до того, как я понял, что поведение под 3 было другим. Я оставляю его, потому что это все еще хороший совет и дает некоторое представление о поведении exec.
Вы никогда не должны напрямую изменять функцию locals()
толковый словарь. Это портит оптимизированные поиски. Смотрите, например, этот вопрос и его ответы
В частности, как объясняет документ Python:
Содержание этого словаря не должно быть изменено; изменения могут не повлиять на значения локальных и свободных переменных, используемых интерпретатором.
Потому что ты звонил exec()
изнутри функции и не передать явно locals()
Вы изменили локальные функции, и, как объясняет документ, это не всегда работает.
Таким образом, Pythonic способ, как уже указывали другие, это явно передавать объекты отображения в exec ().
Python 2.7
Когда можно изменить locals()
? Один ответ - когда вы создаете класс - в этот момент это просто другой словарь:
code = """
g = 5
x = [g for i in range(5)]
"""
class Foo(object):
exec(code)
print Foo.x, Foo.g
[5, 5, 5, 5, 5] 5
РЕДАКТИРОВАТЬ - Python 3 Как другие указывают, как представляется, ошибка с locals()
здесь, независимо от того, находитесь ли вы внутри функции. Вы можете обойти это, передав только один параметр для глобальных переменных. Документация Python объясняет, что если вы передадите только один dict, он будет использоваться как для глобального, так и для локального доступа (это действительно то же самое, что если ваш код не выполняется в определении функции или класса - нет locals()
). Так что ошибка связана с locals()
не появляется в этом случае.
Пример класса выше будет:
code = """
g = 5
x = [g for i in range(5)]
"""
class Foo(object):
exec(code, vars())
print(Foo.x, Foo.g)
Хорошо! Некоторые смотрели вокруг, и это выглядит как ваша линия x = [g for i in range(5)]
пытается создать новое и неинициализированное значение g
вместо того, чтобы использовать тот, который вы определили ранее.
Питоническое исправление - передать ваши возможности вашему exec()
вот так:
def f(code,globals,locals):
exec(code,globals,locals)
code = """
g = 5
x = [g for i in range(5)]
print(x)
"""
f(code,globals(),locals())
Это был очень хороший вопрос. Я многому научился, ответив на него.
Ссылка это для получения дополнительной информации о exec()
: https://docs.python.org/3/library/functions.html
Сокращенная версия была предложена @Pynchia и определяет globals()
при звонке exec()
в рамках функции.
def f(code):
exec(code,globals())
code = """
g = 5
x = [g for i in range(5)]
print(x)
"""
f(code)