Say I have three dictionaries a
, b
and c
. I want to exec()
a code snippet where a
is the globals, b
is the nonlocals and c
is the locals. That is no problem for the globals and locals, as I just have to use exec(code, a, c)
-- but what about b
? How can I make the values in b
visible to the code snippet as nonlocal variables?
I think this clarifies the concept:
assert globals() == a and locals() == a
def foo():
assert globals() == a and locals() == b
def bar():
assert globals() == a and locals() == c
exec(code)
While it is not exactly executing Python code with nonlocals, I was able to rewrite the AST so that it behaves the way I want. Every variable that is being accessed and that is not locally defined in a function or class scope is rewritten to access a mapping instead.
This is an example that demonstrates a possible use case where a
is the dictionary that takes precedence over b
and will receive all variable assignments, but the values of b
are still taken into account if not shadowed by a
.
from nr.datastructures.chaindict import ChainDict
from nr.ast.dynamic_eval import dynamic_exec
d1 = {'a': 42}
d2 = {'b': 'spam'}
code ='''
print(a, b) # prints 42 spam
a = 'egg'
b = 'ham'
'''
dynamic_exec(code, ChainDict(d1, d2))
assert d1 == {'a': 'egg', 'b': 'ham'}, d1
assert d2 == {'b': 'spam'}, d2
Available with nr v2.0.4 (disclaimer: I am the developer of that package).
A little bit more detail
The dynamic_exec()
function will parse the Python code using ast.parse()
and then apply an ast.NodeTransformer
which rewrites global variable names.
Example:
import os
from os import path
parent_dir = path.dirname(__file__)
def main():
filename = path.join(parent_dir, 'foo.py')
print(filename)
This will be turned into an AST that is semantically equivalent to
import os; __dict__['os'] = os
from os import path; __dict__['path'] = path
__dict__['parent_dir'] = __dict__['path'].dirname(__dict__['__file__'])
def main():
filename = __dict__['path'].join(__dict__['parent_dir'], 'foo.py')
__dict__['print'](filename)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With