Поведение функции exec в Python 2 и Python 3

Следующий код дает различный вывод в Python2 и в Python3:

from sys import version

print(version)

def execute(a, st):
    b = 42
    exec("b = {}\nprint('b:', b)".format(st))
    print(b)
a = 1.
execute(a, "1.E6*a")

Python2 Отпечатки:

2.7.2 (default, Jun 12 2011, 15:08:59) [MSC v.1500 32 bit (Intel)]
('b:', 1000000.0)
1000000.0

Python3 Отпечатки:

3.2.3 (default, Apr 11 2012, 07:15:24) [MSC v.1500 32 bit (Intel)]
b: 1000000.0
42

Почему Python2 привязывает переменную b внутри функции execute к значениям в строке функции exec, а Python3 не делает этого? Как я могу достичь поведения Python2 в Python3? Я уже пытался передать словари для глобальных и локальных пользователей функции exec в Python3, но пока ничего не работало.

--- EDIT ---

После прочтения ответа Martijns я дополнительно проанализировал это с помощью Python3. В следующем примере я даю locals() dictionay как d - exec, но d['b'] печатает что-то еще, чем просто печать b.

from sys import version

print(version)

def execute(a, st):
    b = 42
    d = locals()
    exec("b = {}\nprint('b:', b)".format(st), globals(), d)
    print(b)                     # This prints 42
    print(d['b'])                # This prints 1000000.0
    print(id(d) == id(locals())) # This prints True
a = 1.
execute(a, "1.E6*a")

3.2.3 (default, Apr 11 2012, 07:15:24) [MSC v.1500 32 bit (Intel)]
b: 1000000.0
42
1000000.0
True

Сравнение идентификаторов d и locals() показывает, что они являются одним и тем же объектом. Но в этих условиях b должен быть таким же, как d['b']. Что не так в моем примере?

Ответ 1

Существует большая разница между exec в Python 2 и exec() в Python 3. Вы рассматриваете exec как функцию, но это действительно утверждение в Python 2.

Из-за этого различия вы не можете изменять локальные переменные в области действия функции в Python 3, используя exec, даже если это было возможно в Python 2. Даже ранее объявленные переменные.

locals() отражает только локальные переменные в одном направлении. Следующее никогда не работало ни в 2, ни в 3:

def foo():
    a = 'spam'
    locals()['a'] = 'ham'
    print(a)              # prints 'spam'

В Python 2 использование оператора exec означало, что компилятор знал, что нужно отключить оптимизацию локальной области (например, переключиться с LOAD_FAST на LOAD_NAME, чтобы искать переменные как в локальной, так и в глобальной областях). Поскольку exec() является функцией, эта опция больше не доступна, а области действия функций теперь всегда оптимизируются.

Более того, в Python 2 оператор exec явно копирует все переменные, найденные в locals() обратно в PyFrame_LocalsToFast функции с помощью PyFrame_LocalsToFast, но только если не были предоставлены параметры globals и locals.

Правильный обходной путь - использовать новое пространство имен (словарь) для вашего вызова exec():

def execute(a, st):
    namespace = {}
    exec("b = {}\nprint('b:', b)".format(st), namespace)
    print(namespace['b'])

Документация exec() очень четко описывает это ограничение:

Примечание. Локальные параметры по умолчанию действуют так, как описано ниже для функции locals(): изменения в словарь локальных параметров по умолчанию не должны предприниматься. Передайте явный словарь locals, если вам нужно увидеть влияние кода на localals после того, как функция exec() вернется.

Ответ 2

Я бы сказал, что это ошибка python3.

def u():
    exec("a=2")
    print(locals()['a'])
u()

печатает "2".

def u():
    exec("a=2")
    a=2
    print(a)
u()

печатает "2".

Но

def u():
    exec("a=2")
    print(locals()['a'])
    a=2
u()

не работает с

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in u
KeyError: 'a'

--- EDIT --- Еще одно интересное поведение:

def u():
    a=1
    l=locals()
    exec("a=2")
    print(l)
u()
def u():
    a=1
    l=locals()
    exec("a=2")
    locals()
    print(l)
u()

выходы

{'l': {...}, 'a': 2}
{'l': {...}, 'a': 1}

А также

def u():
    l=locals()
    exec("a=2")
    print(l)
    print(locals())
u()
def u():
    l=locals()
    exec("a=2")
    print(l)
    print(locals())
    a=1
u()

выходы

{'l': {...}, 'a': 2}
{'l': {...}, 'a': 2}
{'l': {...}, 'a': 2}
{'l': {...}}

По-видимому, действие exec на locals следующее:

  • Если переменная задана в пределах exec, и эта переменная была локальной переменной, то exec изменяет внутренний словарь (тот, который возвращается locals()) и не возвращает его в исходное состояние. Вызов locals() обновляет словарь (как описано в разделе 2 документации python), а значение, установленное в exec, забывается. Необходимость вызова locals() для обновления словаря не является ошибкой python3, поскольку она документирована, но она не интуитивно понятна. Более того, тот факт, что изменения локалей внутри exec не меняют locals функции, является документированной разницей с python2 (в документации говорится: "Передайте явный словарь locals, если вам нужно увидеть эффекты кода на локальных языках после функции exec() возвращает" ), и я предпочитаю поведение python2.
  • Если переменная установлена ​​в exec, и эта переменная ранее не существовала, то exec изменяет внутренний словарь, если после этого переменная не будет установлена. Кажется, что есть ошибка в том, как locals() обновляет словарь; эта ошибка дает доступ к значению, установленному в exec, вызывая locals() после exec.

Ответ 3

Я боюсь, что я не могу это объяснить точно, но в основном это связано с тем, что функция b внутри функции локальна, а exec(), как представляется, назначает глобальный b. Вам нужно объявить b, чтобы быть глобальным внутри функции, и внутри оператора exec.

Попробуйте следующее:

from sys import version

print(version)

def execute1(a, st):
    b = 42
    exec("b = {}\nprint('b:', b)".format(st))
    print(b)

def execute2(a, st):
    global b
    b = 42
    exec("global b; b = {}\nprint('b:', b)".format(st))
    print(b)

a = 1.
execute1(a, "1.E6*a")
print()
execute2(a, "1.E6*a")
print()
b = 42
exec("b = {}\nprint('b:', b)".format('1.E6*a'))
print(b)

Что дает мне

3.3.0 (default, Oct  5 2012, 11:34:49) 
[GCC 4.4.5]
b: 1000000.0
42

b: 1000000.0
1000000.0

b: 1000000.0
1000000.0

Вы можете видеть, что вне функции глобальный b автоматически подбирается. Внутри функции вы печатаете локальный b.

Обратите внимание, что я бы подумал, что exec() всегда использует глобальный b сначала, так что в execute2() вам не нужно объявлять его внутри функции exec(). Но я считаю, что это не сработает (это часть, которую я не могу точно объяснить).

Ответ 4

Подводя итог:

  • Нет ошибок в Python 2 и в Python 3
  • Различное поведение exec связано с exec, являющимся выражением в Python 2, тогда как оно стало функцией в Python 3.

Обратите внимание:

Я ничего нового здесь не рассказываю. Это всего лишь собрание правды там были найдены во всех других ответах и ​​комментариях. Все, что я пробую здесь, - это привести свет к некоторым более темным деталям.

Единственное различие между Python 2 и Python 3 заключается в том, что, действительно, exec может изменить локальную область функции включения в Python 2 (поскольку это оператор и может получить доступ к текущей локальной области) и не может сделать этого больше в Python 3 (потому что теперь это функция, поэтому запускает в ней собственную локальную область).

Однако раздражение не имеет ничего общего с оператором exec, оно связано только с одной особенностью поведения:

locals() возвращает что-то, что я хочу назвать "изменяемым по объему одноэлементом, который после вызова locals() всегда ссылается только на все переменные в локальной области".

Обратите внимание, что поведение locals() не изменилось между Python 2 и 3. Таким образом, это поведение вместе с изменением того, как работает exec, выглядит как неустойчивое, но не так, поскольку оно просто раскрывает некоторые подробности, который всегда был там.

Что означает "изменяемый по объему singleton, который ссылается на переменные в локальной области"?

  • Это scope-wise singleton, так как независимо от того, как часто вы вызываете locals() в той же области видимости, возвращаемый объект всегда один и тот же.
    • Следовательно, наблюдение, что id(d) == id(locals()), потому что d и locals() относятся к одному и тому же объекту, одному и тому же объекту, так как может быть только один (в другой области вы получаете другой объект, но в в той же области видимости вы видите только один).
  • Это mutable, так как это обычный объект, поэтому вы можете его изменить.
    • locals() заставляет все записи в объекте ссылаться на переменные в локальной области снова.
    • Если вы что-то изменяете в объекте (через d), это изменяет объект, поскольку он является обычным изменяемым объектом.
  • Эти изменения синглтона не распространяются обратно в локальную область, поскольку все записи в объекте references to the variables in the local scope. Поэтому, если вы изменяете записи, это изменяет одноэлементный объект, а не содержимое того, где "указатели указывали, прежде чем изменять ссылку" (следовательно, вы не изменяете локальную переменную).

    • В Python строки и числа не изменяются. Это означает, что если вы присваиваете что-то записи, вы не изменяете объект, на который указывает запись, вы вводите новый объект и назначаете ссылку на запись. Пример:

      a = 1
      d = locals()
      d['a'] = 300
      # d['a']==300
      locals()
      # d['a']==1
      

    Помимо оптимизации это делает:

    • Создать новый объект Number (1) - это еще один синглтон, BTW.
    • сохраните указатель на это число (1) в LOCALS['a']
      (где LOCALS - внутренняя локальная область)
    • Если этого еще не существует, создайте объект SINGLETON
    • update SINGLETON, поэтому он ссылается на все записи в LOCALS
    • сохранить указатель SINGLETON в LOCALS['d']
    • Создать номер (300), который не является одиночным, BTW.
    • сохраните указатель на эти числа (300) на d['a']
    • поэтому обновляется SINGLETON.
    • но LOCALS обновлен не поэтому локальная переменная a или LOCALS['a'] по-прежнему равна Number (1)
    • Теперь locals() вызывается снова, обновляется SINGLETON.
    • Как d относится к SINGLETON, а не LOCALS, d тоже!

Подробнее об этой удивительной детали, почему 1 является одноэлементным, а 300 - нет, см. fooobar.com/info/11976/...

Но, пожалуйста, не забывайте: номера неизменны, поэтому, если вы попытаетесь изменить число на другое значение, вы фактически создадите другой объект.

Вывод:

Вы не можете вернуть поведение exec Python 2 в Python 3 (за исключением изменения кода), так как больше нет возможности изменять локальные переменные за пределами потока программы.

Однако вы можете привести поведение Python 3 в Python 2, чтобы вы могли писать программы, которые работают одинаково, независимо от того, работают ли они с Python 3 или Python 2. Это происходит потому, что в (более новом) Python 2 вы можете использовать exec с функциями, аналогичными аргументам (на самом деле, это 2- или 3-кортеж), и позволяет использовать тот же синтаксис с той же семантикой, что и у Python 3:

exec "code"

(который работает только в Python 2) становится (что работает для Python 2 и 3):

exec("code", globals(), locals())

Но будьте осторожны, что "code" не может больше изменять локальную оболочку таким образом. См. Также https://docs.python.org/2/reference/simple_stmts.html#exec

Некоторые самые последние слова:

Смена exec в Python 3 хороша. Из-за оптимизации.

В Python 2 вам не удалось оптимизировать exec, потому что состояние всех локальных переменных, содержащих неизменяемое содержимое, могло непредсказуемо изменяться. Этого больше не может быть. Теперь обычные правила функций invocations относятся к exec() как и ко всем другим функциям.