我有一个名为let
的函数,它修改调用命名空间以插入一个新变量。
def let(**nameValuePair):
from inspect import stack
name, value = nameValuePair.items()[0]
stack()[1][0].f_locals[name] = value
return value
这个想法是它允许你在任何你想要的地方插入赋值语句,即使Python中通常不允许赋值语句(虽然它需要let()
的5个额外字符。
从全局命名空间调用时,这非常有效。
>>> let(outside = 'World')
>>> print(outside)
World
此操作失败,错误为NameError: global name 'hello' is not defined
:
>>> def breaker():
... let(hello = 'World')
... print(hello)
...
>>> breaker()
您可能会得出结论,问题出在我的let
函数上。它不是 - 我的功能表现完美。告诉你:
>>> def fine():
... let(another = 'World')
... print(locals()['another'])
...
>>> fine()
World
问题实际上来自Python解释器编译breaker
时。为了演示,我将创建另一个与breaker
完全相同的函数,但使用普通赋值而不是let
,然后我用{{1}反编译两者}。
dis
问题是,编译器正在查看>>> def normal():
... hello = 'World'
... print(hello)
...
>>> normal()
World
>>> from dis import dis
>>> print(dis(normal))
0 LOAD_CONST 1 ('World')
3 STORE_FAST 0 (hello)
6 LOAD_FAST 0 (hello)
9 PRINT_ITEM
10 PRINT_NEWLINE
11 LOAD_CONST 0 (None)
14 RETURN_VALUE
>>> print(dis(breaker))
0 LOAD_GLOBAL 0 (let)
3 LOAD_CONST 1 ('hello')
6 LOAD_CONST 2 ('World')
9 CALL_FUNCTION 256
12 POP_TOP
13 LOAD_GLOBAL 2 (hello) # <= This is the real problem.
16 PRINT_ITEM
17 PRINT_NEWLINE
18 LOAD_CONST 0 (None)
19 RETURN_VALUE
,并没有看到breaker
被分配到本地范围内的任何位置,因此假设它必须是全局变量。< / p>
我该怎么办?当我的函数被调用时,我可以换掉我指出的有问题的行并将其替换为:
hello
我知道我可以通过简单地将变量存储在全局范围中来快速解决当前问题,但这可能会导致许多微妙的错误。仅举几例:
...实际上,如果我可以更改已编译的代码,我甚至可以在LOAD_GLOBAL 1 (locals)
CALL_FUNCTION 0
LOAD_CONST 1 ('hello') # Or whatever the name of the variable is
BINARY_SUBSCR
之后添加STORE_GLOBAL
或DELETE_GLOBAL
。将前全局变量缓存到别处或什么?
答案 0 :(得分:3)
locals()
的文档说明如下:
注意不应修改此词典的内容;变化 可能不会影响使用的本地和自由变量的值 解释
我检查了inspect
关于f_locals
的文档中没有明确说明,但我不明白为什么它会有所不同。
答案 1 :(得分:1)
短版本是,你做不到。你不能在3.x中做到这一点,你不能在2.x的最新版本中做到这一点,如果你能够可靠地做到这一点我会感到惊讶。您将来也无法做到这一点,因为局部变量太容易成为优化的目标。一个编译器无法实现这一点,更不用说所有的python编译器了。
另一方面,也许还有另一种方法可以做你想做的事情。那是什么?