Как обновить локальные переменные при использовании вызова `exec`?
Я думал, что это выведет 3, но оно выводит 1:
# Python3
def f():
a = 1
exec("a = 3")
print(a)
f()
# 1 Expected 3
Переведено автоматически
Ответ 1
Эта проблема частично обсуждается в списке ошибок Python3. В конечном итоге, чтобы добиться такого поведения, вам нужно выполнить:
def foo():
ldict = {}
exec("a=3",globals(),ldict)
a = ldict['a']
print(a)
И если вы проверите документацию Python3 на exec
, вы увидите следующее примечание:
Локальные переменные по умолчанию действуют так, как описано для функции
locals()
ниже: не следует пытаться вносить изменения в словарь локальных переменных по умолчанию. Передайте явный словарь локальных переменных, если вам нужно увидеть влияние кода на локальные переменные после возврата функции exec().
Это означает, что one-argument exec
не может безопасно выполнять какие-либо операции, которые связывают локальные переменные, включая назначение переменных, импорт, определения функций, определения классов и т.д. Он может присваиваться глобальным переменным, если использует global
объявление, но не локальным.
Возвращаясь к конкретному сообщению в отчете об ошибке, Георг Брандл говорит:
Изменение локальных значений функции "на лету " невозможно без нескольких последствий: обычно локальные значения функции хранятся не в словаре, а в массиве, индексы которого определяются во время компиляции из известных локальных значений. Это, по крайней мере, сталкивается с новыми локальными переменными, добавленными exec. Старый оператор exec обошел это, потому что компилятор знал, что если exec без аргументов globals / locals встречается в функции, это пространство имен будет "неоптимизированным", т. Е. Не использующим массив 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
Ответ 2
If you are inside a method, you can do so:
# python 2 or 3
class Thing():
def __init__(self):
exec('self.foo = 2')
x = Thing()
print(x.foo)
Ответ 3
The reason that you can't change local variables within a function using exec
in that way, and why exec
acts the way it does, can be summarized as following:
exec
is a function that shares its local scope with the scope of the most inner scope in which it's called.- Whenever you define a new object within a function's scope it'll be accessible in its local namespace, i.e. it will modify the
local()
dictionary. When you define a new object inexec
what it does is roughly equivalent to following:
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
is a temporary namespace that resets after each instantiation (each time you call the exec
).
- Python starts looking up for the names from local namespace. It's known as LEGB manner. Python starts from Local namespce then looks into the Enclosing scopes, then Global and at the end it looks up the names within Buit-in namespace.
A more comprehensive example would be something like following:
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)")
Output:
{'l_var': 10}
{'l_var': 10}
locals are the same.
{'l_var': 10, 'g_var': 222}
after adding g_var
and changing the l_var
it only adds g_var
and left the l_var
unchanged.
{'l_var': 111, 'g_var': 222}
l_var
is changed because we are changing and printing the locals in one instantiation ( one call to exec).
{'l_var': 10, 'g_var': 222}
{'l_var': 10, 'g_var': 222}
In both function's locals and exec's local l_var
is unchanged and g_var
is added.
Inner function:
{}
{'inner_var': 100}
{'inner_var': 100}
inner_function
's local is same as exec's local.
['g_var', 'test']
global is only contain g_var
and function name (after excluding the special methods).
---------------------
(5, 10)
5