代码之家  ›  专栏  ›  技术社区  ›  ilya n.

具有任意局部变量的exec()字节码?

  •  3
  • ilya n.  · 技术社区  · 15 年前

    例如,假设我想要执行代码

        value += 5
    

    在我自己的名称空间中(因此结果本质上是 mydict['value'] += 5 exec() ,但我必须在那里传递一个字符串:

        exec('value += 5', mydict) 
    

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

        def block():
            value += 5
    
        ???(block, mydict)
    

    ? 最后一行的明显候选人是 exec(block.__code__, mydict) ,但运气不好:它提高了 UnboundLocalError value . 我相信它基本上是执行的 block() 块内的代码 ,所以作业不容易,对吗?

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

    仅供参考,我得到这个问题是因为 this thread . 另外,这也是为什么有些人(我还没有决定)需要新语法的原因

        using mydict: 
            value += 5
    

    请注意,这不会引发错误,但不会改变 mydict 要么:

        def block(value = 0):
            value += 5
    
        block(**mydict)
    
    4 回复  |  直到 15 年前
        1
  •  7
  •   Alex Martelli    15 年前

    您可以将字节码而不是字符串传递给 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        
    

    …函数中的代码经过优化以使用快速版本,而这些版本在传递给的dict上不起作用 . 如果您以某种方式使用_FAST指令以字节码开始,您可以将其修补为使用_NAME类型,例如 bytecodehacks 或者类似的方法。

        2
  •  3
  •   John Millikin    15 年前

    global 关键字强制对要从块内修改的任何变量进行动态范围限定:

    def block():
        global value
        value += 5
    
    mydict = {"value": 42}
    exec(block.__code__, mydict)
    print(mydict["value"])
    
        3
  •  3
  •   u0b34a0f6ae    15 年前

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

    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 decorator

        4
  •  0
  •   ilya n.    15 年前

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

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

    change 是一个元类 __prepare__ 读字典和谁的 __new__

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

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