告诉编译器一个它不知道的本地?

时间:2016-02-22 03:41:55

标签: python variable-assignment

我有一个名为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

我知道我可以通过简单地将变量存储在全局范围中来快速解决当前问题,但这可能会导致许多微妙的错误。仅举几例:

  1. 全局变量被覆盖。
  2. 对象可能会在内存中持续存在的时间远远超过预期。
  3. 否则将是一个错字的东西,而是有效的。 (诚​​然,这个似乎不太可能)。
  4. ...实际上,如果我可以更改已编译的代码,我甚至可以在LOAD_GLOBAL 1 (locals) CALL_FUNCTION 0 LOAD_CONST 1 ('hello') # Or whatever the name of the variable is BINARY_SUBSCR 之后添加STORE_GLOBALDELETE_GLOBAL。将前全局变量缓存到别处或什么?

2 个答案:

答案 0 :(得分:3)

locals()的文档说明如下:

  

注意不应修改此词典的内容;变化   可能不会影响使用的本地和自由变量的值   解释

我检查了inspect关于f_locals的文档中没有明确说明,但我不明白为什么它会有所不同。

答案 1 :(得分:1)

短版本是,你做不到。你不能在3.x中做到这一点,你不能在2.x的最新版本中做到这一点,如果你能够可靠地做到这一点我会感到惊讶。您将来也无法做到这一点,因为局部变量太容易成为优化的目标。一个编译器无法实现这一点,更不用说所有的python编译器了。

另一方面,也许还有另一种方法可以做你想做的事情。那是什么?