3

假设我要执行代码,例如

    value += 5

在我自己的命名空间内(所以结果本质上是mydict['value'] += 5)。有一个函数exec(),但我必须在那里传递一个字符串:

    exec('value += 5', mydict) 

并将语句作为字符串传递似乎很奇怪(例如,它不是那样着色的)。可以这样做:

    def block():
        value += 5

    ???(block, mydict)

? 最后一行的明显候选者是exec(block.__code__, mydict),但没有运气:它提高UnboundLocalErrorvalue. 我相信它基本上是执行的block(),而不是块内的代码,所以分配并不容易——对吗?

当然,另一种可能的解决方案是拆卸block.__code__...

仅供参考,因为这个线程,我得到了这个问题。此外,这就是为什么一些(我不确定)需要新语法的原因

    using mydict: 
        value += 5

请注意这不会引发错误但也不会改变mydict

    def block(value = 0):
        value += 5

    block(**mydict)
4

4 回答 4

7

您可以将字节码而不是字符串传递给exec,您只需要为此目的制作正确的字节码:

>>> bytecode = compile('value += 5', '<string>', 'exec')
>>> mydict = {'value': 23}
>>> exec(bytecode, mydict)
>>> mydict['value']
28

具体来说, ...:

>>> import dis
>>> dis.dis(bytecode)
  1           0 LOAD_NAME                0 (value)
              3 LOAD_CONST               0 (5)
              6 INPLACE_ADD         
              7 STORE_NAME               0 (value)
             10 LOAD_CONST               1 (None)
             13 RETURN_VALUE        

加载和存储指令必须具有 _NAME 说服力,这compile使得它们如此,而......:

>>> def f(): value += 5
... 
>>> dis.dis(f.func_code)
  1           0 LOAD_FAST                0 (value)
              3 LOAD_CONST               1 (5)
              6 INPLACE_ADD         
              7 STORE_FAST               0 (value)
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE        

...函数中的代码经过优化以使用 _FAST 版本,而这些代码不适用于传递给exec. 如果您以某种方式使用 _FAST 指令从字节码开始,则可以将其修补为使用 _NAME 类型,例如使用bytecodehacks或一些类似的方法。

于 2009-08-14T22:25:28.063 回答
3

使用global关键字对要从块内修改的任何变量强制动态范围:

def block():
    global value
    value += 5

mydict = {"value": 42}
exec(block.__code__, mydict)
print(mydict["value"])
于 2009-08-15T00:59:58.037 回答
3

这是一个疯狂的装饰器来创建这样一个使用“自定义本地人”的块。实际上,将函数内部的所有变量访问转换为全局访问,并使用自定义本地字典作为环境评估结果是一种快速的技巧。

import dis
import functools
import types
import string

def withlocals(func):
    """Decorator for executing a block with custom "local" variables.

    The decorated function takes one argument: its scope dictionary.

    >>> @withlocals
    ... def block():
    ...     counter += 1
    ...     luckynumber = 88

    >>> d = {"counter": 1}
    >>> block(d)
    >>> d["counter"]
    2
    >>> d["luckynumber"]
    88
    """
    def opstr(*opnames):
        return "".join([chr(dis.opmap[N]) for N in opnames])

    translation_table = string.maketrans(
            opstr("LOAD_FAST", "STORE_FAST"),
            opstr("LOAD_GLOBAL", "STORE_GLOBAL"))

    c = func.func_code
    newcode = types.CodeType(c.co_argcount,
                             0, # co_nlocals
                             c.co_stacksize,
                             c.co_flags,
                             c.co_code.translate(translation_table),
                             c.co_consts,
                             c.co_varnames, # co_names, name of global vars
                             (), # co_varnames
                             c.co_filename,
                             c.co_name,
                             c.co_firstlineno,
                             c.co_lnotab)

    @functools.wraps(func)
    def wrapper(mylocals):
        return eval(newcode, mylocals)
    return wrapper

if __name__ == '__main__':
    import doctest
    doctest.testmod()

这只是某人对goto 装饰器的绝妙配方的猴子补丁改编

于 2009-12-04T04:52:37.740 回答
0

从上面 S.Lott 的评论中,我想我得到了使用创建新类来回答的想法。

class _(__metaclass__ = change(mydict)):
    value += 1
    ...

wherechange是一个__prepare__读取字典和__new__更新字典的元类。

为了重用,下面的代码片段可以工作,但它有点难看:

def increase_value(d):
    class _(__metaclass__ = change(d)):
        value += 1
        ...

increase_value(mydict)
于 2009-08-21T22:54:06.483 回答