修改 `locals()` 或 `frame.f_locals` 中的 *existing* 变量
Modify *existing* variable in `locals()` or `frame.f_locals`
我发现了一些与此问题相关的模糊问题,但没有针对 CPython 的任何干净且具体的解决方案。我假设 "valid" 解决方案是特定于解释器的。
首先是我认为我理解的事情:
locals()
给出一个不可修改的字典。
- 一个函数可以(并且确实)使用某种优化来访问它的局部变量
frame.f_locals
给出了一个类似于 locals()
的字典,但不太容易通过 exec
出现骇人听闻的事情。或者至少我不太能够做像 locals()['var'] = value ; exec ""
这样的骇人听闻的未记录的事情
exec
能够对局部变量做一些奇怪的事情,但它不可靠——例如我在某处读到它在 Python 3 中不起作用。尚未测试。
所以我明白,考虑到这些限制,向局部变量添加 额外变量永远不会安全,因为它会破坏解释器结构。
但是,应该可以更改已经存在的变量,不是吗?
我考虑过的事情
- 在函数
f
中,可以访问 f.func_code.co_nlocals
和 f.func_code.co_varnames
。
- 在一个框架中,可以通过
frame.f_locals
访问/检查/读取变量。这是通过 sys.settrace
. 设置跟踪器的用例
- 考虑到设置跟踪的用例并将其用于 "do things" 在给定某个触发器或其他任何情况下的局部变量中,可以轻松访问框架所在的函数。
变量应该在某个地方,最好是可写的...但我找不到它。即使它是一个数组(为了解释器的高效访问),或者我需要一些额外的 C 特定布线,我也准备好接受它。
我怎样才能从跟踪函数或装饰包装函数或类似的东西实现对变量的修改?
一个完整的解决方案当然会受到赞赏,但即使是一些指针也会对我有很大帮助,因为我被困在这里有很多不可写的字典:-/
编辑:Hackish exec
正在做类似 this or this
的事情
它存在一个未记录的C-API要求做这样的事情:
PyFrame_LocalsToFast
this PyDev blog post 中还有一些讨论。基本思路好像是:
import ctypes
...
frame.f_locals.update({
'a': 'newvalue',
'b': other_local_value,
})
ctypes.pythonapi.PyFrame_LocalsToFast(
ctypes.py_object(frame), ctypes.c_int(0))
我还没有测试这是否按预期工作。
请注意,如果要求只是修改现有变量,可能有一些方法可以直接访问 Fast
,以避免间接访问。但是,由于这似乎主要是 non-documented API,源代码是文档资源。
根据 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()
输出如下:
- 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
我发现了一些与此问题相关的模糊问题,但没有针对 CPython 的任何干净且具体的解决方案。我假设 "valid" 解决方案是特定于解释器的。
首先是我认为我理解的事情:
locals()
给出一个不可修改的字典。- 一个函数可以(并且确实)使用某种优化来访问它的局部变量
frame.f_locals
给出了一个类似于locals()
的字典,但不太容易通过exec
出现骇人听闻的事情。或者至少我不太能够做像locals()['var'] = value ; exec ""
这样的骇人听闻的未记录的事情
exec
能够对局部变量做一些奇怪的事情,但它不可靠——例如我在某处读到它在 Python 3 中不起作用。尚未测试。
所以我明白,考虑到这些限制,向局部变量添加 额外变量永远不会安全,因为它会破坏解释器结构。
但是,应该可以更改已经存在的变量,不是吗?
我考虑过的事情
- 在函数
f
中,可以访问f.func_code.co_nlocals
和f.func_code.co_varnames
。 - 在一个框架中,可以通过
frame.f_locals
访问/检查/读取变量。这是通过sys.settrace
. 设置跟踪器的用例
- 考虑到设置跟踪的用例并将其用于 "do things" 在给定某个触发器或其他任何情况下的局部变量中,可以轻松访问框架所在的函数。
变量应该在某个地方,最好是可写的...但我找不到它。即使它是一个数组(为了解释器的高效访问),或者我需要一些额外的 C 特定布线,我也准备好接受它。
我怎样才能从跟踪函数或装饰包装函数或类似的东西实现对变量的修改?
一个完整的解决方案当然会受到赞赏,但即使是一些指针也会对我有很大帮助,因为我被困在这里有很多不可写的字典:-/
编辑:Hackish exec
正在做类似 this or this
它存在一个未记录的C-API要求做这样的事情:
PyFrame_LocalsToFast
this PyDev blog post 中还有一些讨论。基本思路好像是:
import ctypes
...
frame.f_locals.update({
'a': 'newvalue',
'b': other_local_value,
})
ctypes.pythonapi.PyFrame_LocalsToFast(
ctypes.py_object(frame), ctypes.c_int(0))
我还没有测试这是否按预期工作。
请注意,如果要求只是修改现有变量,可能有一些方法可以直接访问 Fast
,以避免间接访问。但是,由于这似乎主要是 non-documented API,源代码是文档资源。
根据 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()
输出如下:
- 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