I wanted to receive some arguments via **kwargs
and convert them to local variables. So I first tried like this:
for k, v in kwargs.items():
cmd = k + ' = ' + stringify(v)
exec(cmd)
Which ... somehow did not work. Trying to figure out why, at some point I had this example:
def stringify(s):
if isinstance(s, str):
return "'" + s + "'"
else:
return str(s)
def print_var(local):
print(local)
def test_func(**kwargs):
for k, v in kwargs.items():
cmd = k + " = " + stringify(v)
print(cmd)
exec(cmd)
print()
print("locals: " + str(locals()))
print()
print("--- locals()['var']:")
print(locals()["foo"])
print(locals()["bar"])
print(locals()["baz"])
print()
print("--- eval")
print(eval("foo"))
print(eval("bar"))
print(eval("baz"))
print()
print("--- exec")
exec("print_var(foo); print_var(bar); print_var(baz)")
print()
try:
foo
except NameError as e:
print("NameError:", e)
try:
print_var(foo)
except NameError as e:
print("NameError:", e)
try:
bar
except NameError as e:
print("NameError:", e)
try:
print_var(bar)
except NameError as e:
print("NameError:", e)
try:
baz
except NameError as e:
print("NameError:", e)
try:
print_var(baz)
except NameError as e:
print("NameError:", e)
if __name__ == "__main__":
kwargs = {"foo": 13, "bar": True, "baz": "whatever"}
test_func(**kwargs)
When executing, I get the following result (Python 3.6.9 in a virtualenv)
foo = 13
bar = True
baz = 'whatever'
locals: {'cmd': "baz = 'whatever'", 'v': 'whatever', 'k': 'baz', 'kwargs': {'foo': 13, 'bar': True, 'baz': 'whatever'}, 'foo': 13, 'bar': True, 'baz': 'whatever'}
--- locals()['var']:
13
True
whatever
--- eval
13
True
whatever
--- exec
13
True
whatever
NameError: name 'foo' is not defined
NameError: name 'foo' is not defined
NameError: name 'bar' is not defined
NameError: name 'bar' is not defined
NameError: name 'baz' is not defined
NameError: name 'baz' is not defined
And ... I don't get why. Why are the variables not available locally, even though they seem to be in the locals
, and can be used by exec
and eval
.
This answer suggests a way if I'm okay with using eval
- Though I'd like to avoid using it.
It was mentioned here that locals are optimized as an array at runtime in Python 3. The linked thread gives slightly more clarity about it as well - though according to it, locals will not be modified. However, this clearly (?) happened here.
So my assumption is: for some reason, two locals
exist, one is probably an array and was created at 'compile'-time, and the other one (the one I'm modifying with exec
) is probably an actually dynamic dict. And that while the new, dynamic one is accessible from eval
and exec
, for some reason it will not be considered when trying to resolve variables 'normally'.
So I guess my questions are:
- Where did I go wrong?
- Which part of 'local variable' did I misunderstand? How is it different in modules?
- Where can I find more information about this behaviour?
- Is there a different way to use variables passed via
**kwargs
as local variables without resorting toeval
when calling them?
Help is appreciated!