关于python:如何动态修改函数的本地命名空间?

How to dynamically modify a function's local namespace?

注意:这个问题假设python 2.7.3。

我正在寻找一种明智的方法来动态地修改函数的本地名称空间,最好是以一种对主体函数增加最少混乱的方式。

我的想法是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import os
from namespace_updater import update_locals

def somefunc(x, y, z):
    # ...
    # ...
    # this and that
    # ...
    # ...

    if os.environ.get('FROBNICATE'):
        from frobnitz import frobnicate
        update_locals(frobnicate(locals()))

    #
    # life goes on, possibly with duly frobnicated local variables...
    # ...
    # ...
    # ...

谢谢!

附:下面是一些不起作用的方法。

最天真的做法是:

1
locals().update(new_locals(locals())

…但是locals()的文档非常明确地警告不要依赖这样的voodoo来修改局部变量,因此请不要将其作为答案提交(除非您可以很好地证明忽略文档的警告)。

下一个天真的尺度是

1
2
for k, v in new_locals(locals()).items():
    exec ('%s = v' % k)

事实上,这样的代码不能"偏离方向"(也就是说,它必须在函数的主体中),这是不理想的。但真正的破坏者是exec ('%s = v' % k)黑客可以导致一些奇怪的错误。

当我写"奇怪的虫子"的时候,我的意思是"那些对我这样的江户人来说看起来很奇怪的虫子"。我对这个黑客的掌握有多脆弱?要回答这个问题,请考虑下面的脚本。它有三种变体:(1)完全如图所示;(2)删除第18行的前导#之后;(3)删除第15行和第18行的第一个#之后(即,对于这个变体,没有注释掉代码)。我无法预测这个脚本的变体(2)和(3)的行为。我甚至无法用超过50%的信心预测变异(1)的行为。这就是我对exec ('%s = v' % k)黑客的脆弱把握。除非您能够自信而正确地预测这个脚本的三个变体的行为(在Python2.7中),否则可以肯定地说,您对情况的掌握和我的一样薄弱,您可能也应该远离exec ('%s = v' % k)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
x = 'global x'                            # 01
y = 'global y'                            # 02
def main():                               # 03
    x = 'local x'                         # 04
    y = 'local y'                         # 05
    run(locals())                         # 06
    print 'OK'                            # 07
    return 0                              # 08
                                          # 09
def run(namespace):                       # 10
    global y                              # 11
    print locals().keys()                 # 12
    for k, v in namespace.items():        # 13
        print '%s <- %r' % (k, v)         # 14
        exec ('%s = v' % k) #in locals()  # 15
    print locals().keys()                 # 16
    x = x                                 # 17
    #z = lambda: k                        # 18
    print x                               # 19
    print y                               # 20
                                          # 21
exit(main())                              # 22


我将介绍我能想到的唯一接近合理的方法,然后我将试图说服你不要使用它。

1
2
3
4
5
6
7
8
9
def process(**kw):
  mycode ="""\
print 'Value of foo is %s' % (foo,)
print 'Value of bar is %s' % (bar,)
"""

  exec mycode in kw

vars = {'foo': 2, 'bar': 3}
process(**vars)

使用这种方法,至少可以防止代码注入攻击。包含代码的"局部变量"的字典是明确指定的,因此在运行exec语句时,您可以完全控制变量空间的大小。您不必侵入函数对象或其他类似对象的内部。

我知道decorator模块在@decorator的实现中使用exec在动态创建的函数中操纵参数名,并且可能还有其他使用它的通用模块。但我只遇到过一种情况,在这种情况下,exec明显战胜了Python中的备选方案,而eval则是其中之一。

我在你的问题上看不到这样的情况。除非上面的mycode需要做一些真正有趣的事情,比如用kw中给出的参数名创建一个函数,否则很可能只需要简单地编写代码,或者在紧要关头使用locals()

1
2
3
4
5
def process(**kw):
  print 'Value of foo is %s' % (kw['foo'],)
  print 'Value of bar is %s' % (kw['bar'],)

process(foo=2, bar=3)


不确定是否可以只使用外部功能。我创建了一个片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def get_module_prefix(mod, localsDict):
    for name, value in localsDict.iteritems():
        if value == mod:
            return name
    raise Exception("Not found")

def get_new_locals(mod, localsDict):
    modulePrefix = get_module_prefix(mod, localsDict)
    stmts = []
    for name in dir(mod):
        if name.startswith('_'):
            continue
        if name not in localsDict:
            continue
        stmts.append("%s = %s.%s" % (name, modulePrefix, name))
    return"
"
.join(stmts)

def func(someName):
    from some.dotted.prefix import some.dotted.name
    #here we update locals
    exec(get_new_locals(some.dotted.name,"some.dotted.name", locals()))
    print locals()
    print someName # value taken from aModule instead of parameter value


func(5)

哪里:

  • get_module_prefix用于查找导入模块的名称,
  • get_new_locals返回可用于更新局部变量的赋值语句,

局部变量的实际更新是在exec(get_new_locals(some.dotted.name, locals()))行中执行的,在该行中,我们只执行赋值语句,在该语句中,我们将值从模块分配给局部变量。

我不确定这是否是你真正想要的。


可能是那样的

1
2
3
4
5
6
def foo():
    print(x)

foo.__globals__["x"] ="Hello Python"

foo()

不幸的是,如果变量已定义,则在函数体中不起作用。

1
2
3
4
5
6
def foo(flag):
    x ="Hello World"
    if flag:
        foo.__globals__["x"] ="Hello Python"

    print(x)

在两个标志中打印Hello World是真是假