Захваченные переменные в "eval" в Python

У меня возникли проблемы с пониманием семантики "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" ? И какова история этого дизайна? (Это кажется абсурдным!)

Ответы

Ответ 1

Я думаю, вы хотите, чтобы ваши созданные функции наследовали локальную среду создаваемой ими функции, а также реальную глобальную среду (функции, которая их создает). То почему вы не любите их ссылаясь на x как глобальное, право?

Далее создается функция "обертка" вокруг желаемой функции, все внутри одной и той же строки exec. Значения локалей создаваемых функций передаются при вызове или повторном вызове оболочки, создавая новое завернутое закрытие.

Код чувствителен к новым переменным, создаваемым в контексте locals. Для некоторых проблем необходимо убедиться, что имена функций и оболочек известны и имеют значения.

def wrap_f(code, gs, ls, wrapper_name, function_name):
    ls[function_name] = ls[wrapper_name] = None
    arglist = ",".join(ls.keys())
    wrapcode = """
def {wrapper_name}({arglist}):
{code}
    return {function_name}
    """.format(wrapper_name=wrapper_name, arglist=arglist, 
               code=code, function_name=function_name)
    exec(wrapcode, gs, ls)
    wrapper = ls[wrapper_name]
    return wrapper, wrapper(**ls)

Итак, чтобы ответить на исходный вопрос, этот код...

def h(x):
    mcode = "    def m(y): return (w, x, y)"  # must be indented 4 spaces.
    mwrap, m = wrap_f(mcode, globals(), locals(), "mwrap", "m")
    return m

w = 1
m = h(2)
print m(3)

... производит этот вывод:

(1, 2, 3)

И этот пример показывает, что делать, когда изменяются локали в функции создателя:

def foo(x):
    barleycode = """
    def barley(y):
        print "barley x =", x
        print "barley y =", y
    """
    barleywrap, barley = wrap_f(barleycode, globals(), locals(), 
                               "barleywrap", "barley")
    barley("this string")
    print

    x = "modified x"
    barley = barleywrap(**locals())
    barley("new arg")
    print

    x = "re-modified x"
    barley("doesn't see the re-mod.")

x = "the global x"

foo("outer arg")

Это приводит к выходу:

barley x = outer arg
barley y = this string

barley x = modified x
barley y = new arg

barley x = modified x
barley y = doesn't see the re-mod.

Ответ 2

Я не уверен, что у меня все получилось, но я постараюсь изо всех сил: Я думаю, когда вы запускаете eval/exec python, он не понимает, что он внутри функции, я действительно не знаю почему. То, что я попытаюсь сделать, это использовать строку формата, подобную этой

k = eval("lambda y: (w, {0}, y)".format(x))

Я не уверен, что эта штука работает. Кроме того, почему вам нужно использовать eval и exec таким образом?