У меня возникли проблемы с пониманием семантики "eval()" и "exec" в Python. (Весь код в этом вопросе ведет себя аналогично в Python 2.7.8 и Python 3.4.2). документация для "eval" говорит:
Если оба [locals и globals] опущены, выражение выполняется в среде, где вызывается eval().
Аналогичный язык для "exec". Я явно не понимаю этого предложения, потому что я ожидал бы, что четыре функции, определенные следующей программой, будут делать то же самое.
def h(x):
ls = locals()
exec('def i(y): return (w, x, y)', globals(), ls)
i = ls['i']
def j(y): return (w, x, y)
k = eval('lambda y: (w, x, y)')
l = lambda y: (w, x, y)
return i, j, k, l
w = 1
i, j, k, l = h(2)
Они не делают.
>>> i(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1, in i
NameError: name 'x' is not defined
>>> j(3)
(1, 2, 3)
>>> k(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1, in <lambda>
NameError: name 'x' is not defined
>>> l(3)
(1, 2, 3)
Разборка кода показывает, почему: "x" рассматривается как глобальная переменная с помощью "eval" и "exec".
from dis import dis
print("This is `i`:")
dis(i)
print("This is `j`:")
dis(j)
print("This is `k`:")
dis(k)
print("This is `l`:")
dis(l)
print("For reference, this is `h`:")
dis(h)
Вывод:
This is `i`:
1 0 LOAD_GLOBAL 0 (w)
3 LOAD_GLOBAL 1 (x)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
This is `j`:
25 0 LOAD_GLOBAL 0 (w)
3 LOAD_DEREF 0 (x)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
This is `k`:
1 0 LOAD_GLOBAL 0 (w)
3 LOAD_GLOBAL 1 (x)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
This is `l`:
27 0 LOAD_GLOBAL 0 (w)
3 LOAD_DEREF 0 (x)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
For reference, this is `h`:
22 0 LOAD_NAME 0 (locals)
3 CALL_FUNCTION 0
6 STORE_FAST 1 (ls)
23 9 LOAD_CONST 1 ('def i(y): return (w, x, y)')
12 LOAD_NAME 1 (globals)
15 CALL_FUNCTION 0
18 LOAD_FAST 1 (ls)
21 EXEC_STMT
24 22 LOAD_FAST 1 (ls)
25 LOAD_CONST 2 ('i')
28 BINARY_SUBSCR
29 STORE_FAST 2 (i)
25 32 LOAD_CLOSURE 0 (x)
35 BUILD_TUPLE 1
38 LOAD_CONST 3 (<code object j at 0x7ffc3843c030, file "test.py", line 25>)
41 MAKE_CLOSURE 0
44 STORE_FAST 3 (j)
26 47 LOAD_NAME 2 (eval)
50 LOAD_CONST 4 ('lambda y: (w, x, y)')
53 CALL_FUNCTION 1
56 STORE_FAST 4 (k)
27 59 LOAD_CLOSURE 0 (x)
62 BUILD_TUPLE 1
65 LOAD_CONST 5 (<code object <lambda> at 0x7ffc3843c3b0, file "test.py", line 27>)
68 MAKE_CLOSURE 0
71 STORE_FAST 5 (l)
28 74 LOAD_FAST 2 (i)
77 LOAD_FAST 3 (j)
80 LOAD_FAST 4 (k)
83 LOAD_FAST 5 (l)
86 BUILD_TUPLE 4
89 RETURN_VALUE
Вопрос
"j" и "l" выше имеют поведение, которое я хочу. Как я могу получить это поведение с помощью "eval" или "exec"?
Отказ 1
Использование класса вместо функции, поскольку внешняя оболочка меняет семантику, но в противоположность желаемому пути. Он превращает "x" в глобальный.
class H:
x = 2
f = staticmethod(eval('lambda y: (w, x, y)'))
H.dis(H.f)
w = 1
H.f(3)
Вывод:
1 0 LOAD_GLOBAL 0 (w)
3 LOAD_GLOBAL 1 (x)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1, in <lambda>
NameError: global name 'x' is not defined
Обтекание "classmethod" или отказ от него как метода несвязанного экземпляра просто ухудшают ситуацию.
Отказ 2
Подстановка "x" с использованием строковой интерполяции для целых чисел:
def h(x):
return eval('lambda y: (w, %r, y)' % x)
k = h(2)
dis(k)
w = 1
k(3)
Вывод:
1 0 LOAD_GLOBAL 0 (w)
3 LOAD_CONST 1 (2)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
(1, 2, 3)
Однако я не хочу предполагать, что "x" можно без потерь преобразовать в строку и обратно. Попытка разбита в следующих примерах:
k = h(lambda: "something")
k = h(open('some_file', 'w'))
cell = ["Wrong value"]
k = h(cell)
cell[0] = "Right value"
k(3)
Отказ 3
Поскольку Python ищет глобальную переменную, одна очевидная попытка - передать "x" в качестве глобальной переменной:
def h(x):
my_globals = {'w': w, 'x': x}
return eval('lambda y: (w, x, y)', my_globals)
k = h(2)
dis(k)
w = 1
k(3)
Вывод:
1 0 LOAD_GLOBAL 0 (w)
3 LOAD_GLOBAL 1 (x)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
(1, 2, 3)
Эта попытка нарушена, потому что она слишком рано считывает значение "w":
w = "Wrong value"
k = h(2)
w = "Right value"
k(3)
Успех 1
В конце концов я нашел подход, который работает, но мне это действительно не нравится:
def h(x):
return eval('lambda x: lambda y: (w, x, y)')(x)
k = h(2)
dis(k)
w = 1
k(3)
Вывод:
1 0 LOAD_GLOBAL 0 (w)
3 LOAD_DEREF 0 (x)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
(1, 2, 3)
В частности, это станет болезненным, если я не знаю полный список локальных переменных, захваченных строкой, которую я передаю в "eval" .
Можете ли вы сделать лучше?
Обновление 2014-12-25
Отказ 4
Ищем больше способов создания локальной переменной "x" , я пробовал это:
def h(x):
ls = locals()
exec('x = x\ndef i(y): return (w, x, y)', globals(), ls)
exec('_ = x\ndef j(y): return (w, x, y)', globals(), ls)
return ls['i'], ls['j'], ls['_'], ls['x']
i, j, check1, check2 = h(2)
assert check1 == 2
assert check2 == 2
w = 1
print("This is `i`:")
dis(i)
print("This is `j`:")
dis(j)
print("i(3) = %r" % (i(3),))
print("j(3) = %r" % (j(3),))
Дополнительное назначение "x" не влияет. Утверждения подтверждают, что "x" находится в словаре локальных жителей, но он не захватывается лямбдами. Вот результат:
This is `i`:
2 0 LOAD_GLOBAL 0 (w)
3 LOAD_GLOBAL 1 (x)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
This is `j`:
2 0 LOAD_GLOBAL 0 (w)
3 LOAD_GLOBAL 1 (x)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
Вызов "i" и "j" приводит к сбою, жалуясь на отсутствие глобальной переменной "x" .
Успех 2
[Edit 2014-12-29: Это удается только для Python 3.]
Другой способ создания локальной переменной:
def h(x):
i = eval('[lambda y: (w, x, y) for x in [x]][0]')
j = eval('[lambda y: (w, x, y) for _ in [x]][0]')
return i, j
i, j = h(2)
w = 1
print("This is `i`:")
dis(i)
print("This is `j`:")
dis(j)
print("i(3) = %r" % (i(3),))
print("j(3) = %r" % (j(3),))
Странно, в этом случае дополнительное присвоение "x" действительно имеет эффект. Это работает, то есть "i" отличается от "j". Вот результат:
This is `i`:
1 0 LOAD_GLOBAL 0 (w)
3 LOAD_DEREF 0 (x)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
This is `j`:
1 0 LOAD_GLOBAL 0 (w)
3 LOAD_GLOBAL 1 (x)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
i(3) = (1, 2, 3)
Вызов "j" выдается из строя, жалуясь на отсутствие глобального "x" , но "i" работает по желанию и имеет правильный байт-код.
Почему это работает, а "Failure 4" выше нет? Каково правило, которое определяет, можно ли захватить локальное "x" ? И какова история этого дизайна? (Это кажется абсурдным!)