使用nonlocals执行Python代码

时间:2018-02-03 12:27:07

标签: python python-3.x

假设我有三个词典abc。我想exec()一个代码段,其中a是全局变量,b是非本地人,c是本地人。对于全局变量和局部变量来说,这不是问题,因为我只需要使用exec(code, a, c) - 但b呢?如何将b中的值显示为代码段作为非本地变量?

我认为这澄清了这个概念:

assert globals() == a and locals() == a
def foo():
  assert globals() == a and locals() == b
  def bar():
    assert globals() == a and locals() == c
    exec(code)

2 个答案:

答案 0 :(得分:1)

虽然它并没有完全用nonlocals执行Python代码,但我能够重写AST以使其行为符合我的要求。正在访问的每个变量以及未在函数或类范围中本地定义的变量都会被重写以代替访问映射。

这是一个示例,演示了一个可能的用例,其中a是优先于b的字典,并将接收所有变量赋值,但b的值仍然被采用如果没有被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

适用于nr v2.0.4 (免责声明:我是该软件包的开发者)

更详细一点

dynamic_exec()函数将使用ast.parse()解析Python代码,然后应用重写全局变量名称的ast.NodeTransformer。 例如:

import os
from os import path

parent_dir = path.dirname(__file__)

def main():
  filename = path.join(parent_dir, 'foo.py')
  print(filename)

这将变成一个在语义上等同于

的AST
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 :(得分:0)

根据@jonrsharpe链接,以及我的测试,你不能。除非你在闭包定义时(例如作为dict参数)制作你称之为闭包可用的nonlocals b。在使用exec时,已经定义了闭包,单元格值不可更改。

编辑:

但是如果你向闭包发送一个可变参数,例如dict,你会获得一些灵活性,因为现在你可以对它采取行动,就像你在闭包单元格值上做的那样:

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())')