#python #pytest #python-exec
#python #python-3.x #exec #локальные переменные
Вопрос:
Я думал, что это выведет 3, но он выводит 1:
# Python3
def f():
a = 1
exec("a = 3")
print(a)
f()
# 1 Expected 3
Комментарии:
1. Какая версия Python? Это 2.6?
2. Печатает 3 на моей машине с помощью python 2.5.4
3. Я получаю 1 в Python 3, думаю, это его версия.
4. Наличие круглых скобок в
print(a)
может указывать на Python 3.x. Я бы попробовал это там, но у меня его нет под рукой.5. Да, это был python 3, извините, что не заметил этого.
Ответ №1:
Эта проблема несколько обсуждается в списке ошибок Python3. В конечном счете, чтобы добиться такого поведения, вам нужно сделать:
def foo():
ldict = {}
exec("a=3",globals(),ldict)
a = ldict['a']
print(a)
И если вы проверите документацию Python3 exec
, вы увидите следующее примечание:
Локальные переменные по умолчанию действуют так, как описано для функции
locals()
ниже: не следует пытаться вносить изменения в словарь локальных переменных по умолчанию. Передайте явный словарь locals, если вам нужно увидеть влияние кода на locals после возврата функции exec ().
Это означает, что один аргумент exec
не может безопасно выполнять какие-либо операции, которые связывают локальные переменные, включая присвоение переменных, импорт, определения функций, определения классов и т.д. Он может присваивать глобальные переменные, если он использует global
объявление, но не локальные.
Возвращаясь к конкретному сообщению в отчете об ошибке, Георг Брандл говорит:
Изменение локальных значений функции «на лету» невозможно без нескольких последствий: обычно локальные значения функции хранятся не в словаре, а в массиве, индексы которого определяются во время компиляции из известных локалей. Это сталкивается, по крайней мере, с новыми локальными объектами, добавленными exec. Старый оператор exec обошел это, потому что компилятор знал, что если exec без глобальных / локальных аргументов встречается в функции, это пространство имен будет «неоптимизированным», т. Е. Не использующим массив locals. Поскольку exec() теперь является обычной функцией, компилятор не знает, к чему может быть привязан «exec», и поэтому не может обрабатывать is специально.
Акцент мой.
Итак, суть в том, что Python3 может лучше оптимизировать использование локальных переменных, не разрешая такое поведение по умолчанию.
И для полноты картины, как упоминалось в комментариях выше, это работает так, как ожидалось в Python 2.X:
Python 2.6.2 (release26-maint, Apr 19 2009, 01:56:41)
[GCC 4.3.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> def f():
... a = 1
... exec "a=3"
... print a
...
>>> f()
3
Комментарии:
1. Я вижу, это проблема с locals (), которая была взломана из exec в python 2.X. Эта проблема не так четко документирована, как хотелось бы. Где — то должно быть указано изменение Exec / locals с 2.X на 3.X docs.python.org/3.1/library/functions.html#exec и я думаю, что у exec должен быть параметр удобства, который обходит эту оптимизацию…
2. @MarkRushakoff Я получаю сообщение об ошибке с вашей реализацией в строке exec: TypeError: объект ‘dict’ не вызывается
3. Невероятно, что разработчики ядра Python ничего не делают для решения этой проблемы каким-либо элегантным способом уже почти 10 лет. Я могу подтвердить, что в августе 2019 года в версии Python 3.7.2 это нежелательное / неожиданное поведение все еще существует.
4. Эти ребята добавили свою мусорную «функцию», разрушили большую гибкость Python 2, и им наплевать на жалобы людей. Вышеупомянутый отчет об ошибке закрыт со статусом»работает для меня» и завершается замечанием Джереми Хилтона: «Python ведет себя так, как задумано, и я думаю, что Георг ответил на все вопросы Дэвида». На самом деле у меня нет слов, как вызывать таких людей.
5. @AnatolyAlekseev: Это задокументировано (в смысле «Изменения локальных параметров по умолчанию не поддерживаются»), и нет хорошего исправления, которое не включало бы восстановление
exec
статуса ключевого слова, регрессии производительности в коде, который не нуждается в этой функции, или выполнение действительно сложных вещей для записи влокальные файлы передаются «реальным» локальным файлам (что может быть непрактично в интерпретаторах, отличных от CPython). Точка есть,exec
есть и всегда была плохой идеей, и в редких случаях, когда вам необходимо достичь описанной функциональности, существуют обходные пути (как описано в этом ответе ).
Ответ №2:
Если вы находитесь внутри метода, вы можете это сделать:
# python 2 or 3
class Thing():
def __init__(self):
exec('self.foo = 2')
x = Thing()
print(x.foo)
Комментарии:
1. ссылка bugs.python.org/issue4831
Ответ №3:
Причина, по которой вы не можете изменять локальные переменные внутри функции, используя exec
таким образом, и почему exec
она действует так, как она действует, может быть обобщена следующим образом:
exec
это функция, которая разделяет свою локальную область видимости с областью самой внутренней области, в которой она вызывается.- Всякий раз, когда вы определяете новый объект в области действия функции, он будет доступен в его локальном пространстве имен, т. Е. Он изменит
local()
словарь. Когда вы определяете новый объектexec
, то, что он делает, примерно эквивалентно следующему:
from copy import copy
class exec_type:
def __init__(self, *args, **kwargs):
# default initializations
# ...
self.temp = copy(locals())
def __setitem__(self, key, value):
if var not in locals():
set_local(key, value)
self.temp[key] = value
temp
это временное пространство имен, которое сбрасывается после каждого создания экземпляра (каждый раз, когда вы вызываете exec
).
- Python начинает поиск имен из локального пространства имен. Это называется способом LEGB. Python начинается с локального namespace, затем просматривает окружающие области, затем глобальные, и в конце он ищет имена внутри встроенного пространства имен.
Более полным примером может быть что-то вроде следующего:
g_var = 5
def test():
l_var = 10
print(locals())
exec("print(locals())")
exec("g_var = 222")
exec("l_var = 111")
exec("print(locals())")
exec("l_var = 111; print(locals())")
exec("print(locals())")
print(locals())
def inner():
exec("print(locals())")
exec("inner_var = 100")
exec("print(locals())")
exec("print([i for i in globals() if '__' not in i])")
print("Inner function: ")
inner()
print("-------" * 3)
return (g_var, l_var)
print(test())
exec("print(g_var)")
Вывод:
{'l_var': 10}
{'l_var': 10}
локальные значения те же.
{'l_var': 10, 'g_var': 222}
после добавления g_var
и изменения l_var
он только добавляет g_var
и оставляет l_var
неизменным.
{'l_var': 111, 'g_var': 222}
l_var
изменяется, потому что мы изменяем и печатаем локальные переменные в одном экземпляре (один вызов exec).
{'l_var': 10, 'g_var': 222}
{'l_var': 10, 'g_var': 222}
Как в локальных функциях, так и в локальных функциях exec l_var
не изменяется и g_var
добавляется.
Inner function:
{}
{'inner_var': 100}
{'inner_var': 100}
inner_function
локальное значение совпадает с локальным значением exec.
['g_var', 'test']
глобальный — это только contain g_var
и имя функции (после исключения специальных методов).
---------------------
(5, 10)
5