Python Exec Code with Nonlocal

Say I have three dictionaries a , b and c . I want exec() piece of code where a are global variables, b are non-locals, and c are local. This is not a problem for global and local residents, as I just need to use exec(code, a, c) , but what about b ? How to make values ​​in b visible to a piece of code as non-local 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) 
+5
source share
2 answers

Until it runs Python code with non-local ones, I was able to rewrite the AST so that it behaves the way I want. Each variable that is accessed and that is not locally defined in the scope of functions or classes is rewritten to access the mapping.

This is an example demonstrating a possible use case where a is a dictionary that takes precedence over b and receives all variable assignments, but the values ​​of b are still taken into account if they are not obscured 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 since nr v2.0.4 (disclaimer: I am the developer of this package).


A bit more

The dynamic_exec() function will parse Python code using ast.parse() , and then apply ast.NodeTransformer , which overwrites the names of global variables. Example:

 import os from os import path parent_dir = path.dirname(__file__) def main(): filename = path.join(parent_dir, 'foo.py') print(filename) 

It will be turned into AST, semantically equivalent

 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) 
+1
source

You can’t follow the @jonrsharpe link and my tests. If you do not make what you call nonlocal b available for closure, at the time you define closure (for example, the dict argument). While you are using exec, a close is already defined with the same cell values.

Edit:

But if you send a volatile argument to close, for example a dict , you get some flexibility, since now you can act on it almost the same as if you were acting on the values ​​of the closure cell:

 def func(b): def inner(): b['count'] += 1 # nonlocal doesn't matter here return b['count'] return inner b = dict(count=100) f1 = func(b) exec('print(f1())') # locals don't matter b['count'] = 200 # now a little change in the environment... exec('print(f1())') 
0
source

Source: https://habr.com/ru/post/1275147/


All Articles