我发现了与此问题相关的一些模糊的问题,但没有找到任何针对CPython的干净且特定的解决方案。我认为“有效”解决方案是特定于解释器的。
首先,我认为我了解:
locals() 给出了不可修改的字典。frame.f_locals给出了locals()类似的字典,但不太容易通过hackhacking exec。或至少我没有能力做些骇人听闻的未证明的事情,例如locals()['var'] = value ; exec ""exec 能够对局部变量做一些奇怪的事情,但它并不可靠-例如,我读到某处它在Python 3中不起作用。未经测试。因此,我了解到,鉴于这些限制,向局部变量添加额外的变量永远是不安全的,因为这会破坏解释器的结构。
但是,应该可以更改已经存在的变量,不是吗?
我考虑过的事情
f,可以访问f.func_code.co_nlocals和f.func_code.co_varnames。frame.f_locals。这是通过设置跟踪器的用例sys.settrace。变量应该在某个地方,最好是可写的...但是我找不到它。即使它是一个数组(用于有效的解释器访问),或者我需要一些额外的C特定接线,我也准备提交。
如何从跟踪函数或修饰的包装函数或类似的东西实现对变量的修改?
完整的解决方案当然会受到赞赏,但即使是某些指针也会对我有很大帮助,因为我在这里遇到了很多不可写的字典:-/
Mar*_*ram 11
它存在一个未公开的 C-API 调用,用于执行以下操作:
PyFrame_LocalsToFast
这篇 PyDev 博客文章中有更多讨论。基本思想似乎是:
import ctypes
...
frame.f_locals.update({
'a': 'newvalue',
'b': other_local_value,
})
ctypes.pythonapi.PyFrame_LocalsToFast(
ctypes.py_object(frame), ctypes.c_int(0))
Run Code Online (Sandbox Code Playgroud)
我还没有测试这是否按预期工作。
请注意,Fast如果要求只是修改现有变量,则可能有某种方法可以直接访问,以避免间接访问。但是,由于这似乎主要是未记录的 API,源代码是文档资源。
小智 7
根据 MariusSiuram 的笔记,我写了一个显示该行为的食谱。
结论是:
所以,这是代码:
import inspect
import ctypes
def parent():
a = 1
z = 'foo'
print('- Trying to add a new variable ---------------')
hack(case=0) # just try to add a new variable 'b'
print(a)
print(z)
assert a == 1
assert z == 'foo'
try:
print (b)
assert False # never is going to reach this point
except NameError, why:
print("ok, global name 'b' is not defined")
print('- Trying to remove an existing variable ------')
hack(case=1)
print(a)
assert a == 2
try:
print (z)
except NameError, why:
print("ok, we've removed the 'z' var")
print('- Trying to update an existing variable ------')
hack(case=2)
print(a)
assert a == 3
def hack(case=0):
frame = inspect.stack()[1][0]
if case == 0:
frame.f_locals['b'] = "don't work"
elif case == 1:
frame.f_locals.pop('z')
frame.f_locals['a'] += 1
else:
frame.f_locals['a'] += 1
# passing c_int(1) will remove and update variables as well
# passing c_int(0) will only update
ctypes.pythonapi.PyFrame_LocalsToFast(
ctypes.py_object(frame),
ctypes.c_int(1))
if __name__ == '__main__':
parent()
Run Code Online (Sandbox Code Playgroud)
输出如下:
- Trying to add a new variable ---------------
1
foo
ok, global name 'b' is not defined
- Trying to remove an existing variable ------
2
foo
- Trying to update an existing variable ------
3
Run Code Online (Sandbox Code Playgroud)