EXEC()字节码任意当地人?
-
16-09-2019 - |
题
假设我要执行的代码,例如
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__
...
仅供参考,我得到了,因为这个线程。同时,这也是为什么一些(我未定)呼吁新的语法
using mydict:
value += 5
请注意这是如何不会引发错误,但不会改变mydict
之一:
def block(value = 0):
value += 5
block(**mydict)
解决方案
您可以传递的,而不是一个字符串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 或一些类似的方法。
其他提示
使用global
关键字来强制要从块内修改任何变量的动态作用域:
def block():
global value
value += 5
mydict = {"value": 42}
exec(block.__code__, mydict)
print(mydict["value"])
下面是一个疯狂装饰来创建使用“定制机”这样的块。在现实中,它是一个快速黑客关闭此功能,以全球访问里面的所有变量访问,并与当地人定制词典中环境评估的结果。
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()
这是一个转到装饰别人的辉煌配方只是一只猴子,修补适应
从美国洛特的评论上面我觉得我得到了使用新类的创建一个答案的想法。
class _(__metaclass__ = change(mydict)):
value += 1
...
其中change
是一个元类,其__prepare__
读字典,并且其__new__
更新字典。
有关重用,代码段下面的工作,但它是一种丑陋的:
def increase_value(d):
class _(__metaclass__ = change(d)):
value += 1
...
increase_value(mydict)