如何动态修改函数的本地名称空间? [英] How to dynamically modify a function's local namespace?

查看:106
本文介绍了如何动态修改函数的本地名称空间?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

NB:这个问题假设使用Python 2.7.3.

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

我的想法看起来像这样:

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...
    # ...
    # ...
    # ...

谢谢!


PS:以下是无效的方法.

最幼稚的方法是:

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

...但是,locals()文档非常明确地警告您依靠此类伏都教来修改局部变量,因此请不要将此作为答案(除非您可以为忽略文档警告提供很好的理由).

天真尺度下的下一步是类似的

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

AFAICT,这样的代码不能脱离常规"(即,必须位于函数的主体中),这不是理想的选择.但是真正的破坏者是exec ('%s = v' % k) hack可能导致一些奇怪的错误.

当我写"bizarre bugs"时,我的意思是对于像我一样难以理解exec ('%s = v' % k)的人来说,看起来怪异的bug".我对这种骇客的掌握程度有多弱?要回答此问题,请考虑以下脚本.它具有三个变体:(1)完全如图所示; (2)在删除第18行的前导#之后; (3)在第15和18行中删除第一个#之后(即,对于此变体,没有代码被注释掉).我无法预测此脚本的变体(2)和(3)的行为.我什至无法以超过50%的置信度预测变量(1)的行为.这就是我对exec ('%s = v' % k) hack的掌握有多脆弱.除非您能自信正确地预测此脚本的三个变体的行为方式(在python 2.7下),否则可以肯定地说,您对情况的掌握与我的一样微弱,因此您可能应避免使用exec ('%s = v' % k)也是.

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

解决方案

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

>

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的实现中使用exec来在动态创建的函数中操纵参数名称,并且可能还有其他通用模块使用它.但是我只遇到过一种情况,其中exec明显胜过Python的替代方案,而另一种情况eval.

我在您的问题中看不到这种情况.除非上面的mycode需要做一些真正时髦的事情,例如创建具有kw中给出的参数名称的函数,否则您可能就可以简单地编写代码,甚至可以在紧要关头使用locals()了. /p>

def process(**kw):
  print 'Value of foo is %s' % (kw['foo'],)
  print 'Value of bar is %s' % (kw['bar'],)

process(foo=2, bar=3)

NB: This question assumes Python 2.7.3.

I'm looking for a sane approach to dynamically modify a function's local namespace, preferably in a way that adds the least clutter to the body function.

What I have in mind would look something like this:

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...
    # ...
    # ...
    # ...

Thanks!


PS: Below are approaches that don't work.

The most naive approach to this would be something like:

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

...but the documentation for locals() very explicitly warns against relying on such voodoo to modify local variables, so please do not submit this as an answer (unless you can make an excellent case for disregarding the documentation's warning).

Next in the naivete scale is something like

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

AFAICT, such code cannot be "out of the way" (i.e., it has to be in the body of the function), which is not ideal. But the real deal-breaker is that the exec ('%s = v' % k) hack can lead to some bizarre bugs.

When I write "bizarre bugs" what I mean is "bugs that look bizarre to someone with as tenuous a grasp of exec ('%s = v' % k) as mine". How tenuous is my grasp of this hack? To answer this, consider the script below. It has three variants: (1) exactly as shown; (2) after deleting the leading # of line 18; (3) after deleting the first # in both lines 15 and 18 (i.e. for this variant, no code is commented out). I could have not predicted the behavior of variants (2) and (3) of this script. I could not have even predicted with more than a 50% confidence the behavior of variant (1). That's how tenuous my grasp of the exec ('%s = v' % k) hack. Unless you can confidently and correctly predict how the three variants of this script will behave (under python 2.7), it is safe to say that your grasp of the situation is about as tenuous as mine, and you probably should stay clear of exec ('%s = v' % k) too.

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

解决方案

I'll present the only approach I can think of that is close to reasonable, and then I'll try to convince you not to use it.

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)

With this approach, you have at least some protection from code-injection attacks. The dictionary containing the "local variables" of the code is specified explicitly, so you have complete control over what the variable space will be when you run the exec statement. You don't have to hack into the internals of function objects or other such.

I know that the decorator module uses exec in the implementation of @decorator to manipulate argument names in dynamically created functions, and there may be other common modules that use it. But I have been in only one situation where exec was a clear win over the alternatives in Python, and one for eval.

I do not see such a situation in your question. Unless mycode from above needs to do something really funky, like create a function with argument names given in kw, chances are you can get away with just writing the code plainly, and maybe using locals() in a pinch.

def process(**kw):
  print 'Value of foo is %s' % (kw['foo'],)
  print 'Value of bar is %s' % (kw['bar'],)

process(foo=2, bar=3)

这篇关于如何动态修改函数的本地名称空间?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆