Ответ 1
Проблема заключается в том, что в 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.