[英]How can I get the values of the locals of a function after it has been executed?
Suppose I have a function like f(a, b, c=None)
. 假设我有一个像
f(a, b, c=None)
这样的函数。 The aim is to call the function like f(*args, **kwargs)
, and then construct a new set of args and kwargs such that: 目的是将函数调用为
f(*args, **kwargs)
,然后构造一组新的args和kwargs,以便:
f(1, 2)
, I should be able to get the tuple (1, 2, None)
and/or the dictionary {'c': None}
. f(1, 2)
,我应该能够获得元组(1, 2, None)
和/或字典{'c': None}
。 f(1, 100000, 3)
and the function does if b > 500: b = 5
modifying the local variable, I should be able to get the the tuple (1, 5, 3)
. f(1, 100000, 3)
, if b > 500: b = 5
修改局部变量,则函数执行,我应该能够得到元组(1, 5, 3)
1,5,3 (1, 5, 3)
。 The aim here is to create aa decorator that finishes the job of a function. 这里的目的是创建一个完成函数工作的装饰器。 The original function acts as a preamble setting up the data for the actual execution, and the decorator finishes the job.
原始函数充当为实际执行设置数据的前导码,装饰器完成作业。
Edit: I'm adding an example of what I'm trying to do. 编辑:我正在添加一个我正在尝试做的例子。 It's a module for making proxies for other classes.
它是一个为其他类创建代理的模块。
class Spam(object):
"""A fictional class that we'll make a proxy for"""
def eggs(self, start, stop, step):
"""A fictional method"""
return range(start, stop, step)
class ProxyForSpam(clsproxy.Proxy): proxy_for = Spam @clsproxy.signature_preamble def eggs(self, start, stop, step=1): start = max(0, start) stop = min(100, stop)
And then, we'll have that: 然后,我们将拥有:
ProxyForSpam().eggs(-10, 200) -> Spam().eggs(0, 100, 1)
ProxyForSpam().eggs(3, 4) -> Spam().eggs(3, 4, 1)
There are two recipes available here , one which requires an external library and another that uses only the standard library. 有两种配方可在这里 ,一个需要外部库,另一个只使用标准库。 They don't quite do what you want, in that they actually modify the function being executed to obtain its
locals()
rather than obtain the locals()
after function execution, which is impossible, since the local stack no longer exists after the function finishes execution. 它们并不能完全按照你想要的方式执行,因为它们实际上修改了正在执行的函数以获取其
locals()
而不是在函数执行后获取locals()
,这是不可能的,因为函数之后不再存在本地堆栈完成执行。
Another option is to see what debuggers, such as WinPDB or even the pdb
module do. 另一种选择是查看调试器,例如WinPDB甚至
pdb
模块。 I suspect they use the inspect
module (possibly along with others), to get the frame inside which a function is executing and retrieve locals()
that way. 我怀疑他们使用
inspect
模块(可能还有其他模块)来获取正在执行函数的框架并以这种方式检索locals()
。
EDIT: After reading some code in the standard library, the file you want to look at is probably bdb.py
, which should be wherever the rest of your Python standard library is. 编辑:在阅读标准库中的一些代码之后,您要查看的文件可能是
bdb.py
,它应该是Python标准库的其余部分。 Specifically, look at set_trace()
and related functions. 具体来说,请查看
set_trace()
和相关函数。 This will give you an idea of how the Python debugger breaks into the class. 这将让您了解Python调试器如何进入类中。 You might even be able to use it directly.
您甚至可以直接使用它。 To get the frame to pass to
set_trace()
look at the inspect
module. 要将帧传递给
set_trace()
,请inspect
模块。
I don't see how you could do this non-intrusively -- after the function is done executing, it doesn't exist any more -- there's no way you can reach inside something that doesn't exist. 我不知道你怎么能非侵入地做这个 - 在函数执行完之后,它不再存在 - 你无法进入内部不存在的东西。
If you can control the functions that are being used, you can do an intrusive approach like 如果您可以控制正在使用的功能,您可以执行类似的侵入式方法
def fn(x, y, z, vars):
'''
vars is an empty dict that we use to pass things back to the caller
'''
x += 1
y -= 1
z *= 2
vars.update(locals())
>>> updated = {}
>>> fn(1, 2, 3, updated)
>>> print updated
{'y': 1, 'x': 2, 'z': 6, 'vars': {...}}
>>>
...or you can just require that those functions return locals()
-- as @Thomas K asks above, what are you really trying to do here? ...或者你可以要求那些函数返回
locals()
- 正如@Thomas K上面提到的那样,你在这里真正想做什么?
I've stumbled upon this very need today and wanted to share my solution. 我今天偶然发现了这个需求,并想分享我的解决方案。
import sys
def call_function_get_frame(func, *args, **kwargs):
"""
Calls the function *func* with the specified arguments and keyword
arguments and snatches its local frame before it actually executes.
"""
frame = None
trace = sys.gettrace()
def snatch_locals(_frame, name, arg):
nonlocal frame
if frame is None and name == 'call':
frame = _frame
sys.settrace(trace)
return trace
sys.settrace(snatch_locals)
try:
result = func(*args, **kwargs)
finally:
sys.settrace(trace)
return frame, result
The idea is to use sys.trace()
to catch the frame of the next 'call'
. 我们的想法是使用
sys.trace()
来捕获下一个'call'
的帧。 Tested on CPython 3.6. 在CPython 3.6上测试过。
Example usage 用法示例
import types
def namespace_decorator(func):
frame, result = call_function_get_frame(func)
try:
module = types.ModuleType(func.__name__)
module.__dict__.update(frame.f_locals)
return module
finally:
del frame
@namespace_decorator
def mynamespace():
eggs = 'spam'
class Bar:
def hello(self):
print("Hello, World!")
assert mynamespace.eggs == 'spam'
mynamespace.Bar().hello()
I have no clue what you want to do with this, it's possible but it's an awful hack... 我不知道你想用它做什么,这是可能的,但它是一个可怕的黑客......
Anyways, I HAVE WARNED YOU(!) , be lucky if such things don't work in your favorite language... 无论如何, 我已经警告过你(!) ,如果这些东西不适合你喜欢的语言,那就很幸运了...
from inspect import getargspec, ismethod
import inspect
def main():
@get_modified_values
def foo(a, f, b):
print a, f, b
a = 10
if a == 2:
return a
f = 'Hello World'
b = 1223
e = 1
c = 2
foo(e, 1000, b = c)
# intercept a function and retrieve the modifed values
def get_modified_values(target):
def wrapper(*args, **kwargs):
# get the applied args
kargs = getcallargs(target, *args, **kwargs)
# get the source code
src = inspect.getsource(target)
lines = src.split('\n')
# oh noes string patching of the function
unindent = len(lines[0]) - len(lines[0].lstrip())
indent = lines[0][:len(lines[0]) - len(lines[0].lstrip())]
lines[0] = ''
lines[1] = indent + 'def _temp(_args, ' + lines[1].split('(')[1]
setter = []
for k in kargs.keys():
setter.append('_args["%s"] = %s' % (k, k))
i = 0
while i < len(lines):
indent = lines[i][:len(lines[i]) - len(lines[i].lstrip())]
if lines[i].find('return ') != -1 or lines[i].find('return\n') != -1:
for e in setter:
lines.insert(i, indent + e)
i += len(setter)
elif i == len(lines) - 2:
for e in setter:
lines.insert(i + 1, indent + e)
break
i += 1
for i in range(0, len(lines)):
lines[i] = lines[i][unindent:]
data = '\n'.join(lines) + "\n"
# setup variables
frame = inspect.currentframe()
loc = inspect.getouterframes(frame)[1][0].f_locals
glob = inspect.getouterframes(frame)[1][0].f_globals
loc['_temp'] = None
# compile patched function and call it
func = compile(data, '<witchstuff>', 'exec')
eval(func, glob, loc)
loc['_temp'](kargs, *args, **kwargs)
# there you go....
print kargs
# >> {'a': 10, 'b': 1223, 'f': 'Hello World'}
return wrapper
# from python 2.7 inspect module
def getcallargs(func, *positional, **named):
"""Get the mapping of arguments to values.
A dict is returned, with keys the function argument names (including the
names of the * and ** arguments, if any), and values the respective bound
values from 'positional' and 'named'."""
args, varargs, varkw, defaults = getargspec(func)
f_name = func.__name__
arg2value = {}
# The following closures are basically because of tuple parameter unpacking.
assigned_tuple_params = []
def assign(arg, value):
if isinstance(arg, str):
arg2value[arg] = value
else:
assigned_tuple_params.append(arg)
value = iter(value)
for i, subarg in enumerate(arg):
try:
subvalue = next(value)
except StopIteration:
raise ValueError('need more than %d %s to unpack' %
(i, 'values' if i > 1 else 'value'))
assign(subarg,subvalue)
try:
next(value)
except StopIteration:
pass
else:
raise ValueError('too many values to unpack')
def is_assigned(arg):
if isinstance(arg,str):
return arg in arg2value
return arg in assigned_tuple_params
if ismethod(func) and func.im_self is not None:
# implicit 'self' (or 'cls' for classmethods) argument
positional = (func.im_self,) + positional
num_pos = len(positional)
num_total = num_pos + len(named)
num_args = len(args)
num_defaults = len(defaults) if defaults else 0
for arg, value in zip(args, positional):
assign(arg, value)
if varargs:
if num_pos > num_args:
assign(varargs, positional[-(num_pos-num_args):])
else:
assign(varargs, ())
elif 0 < num_args < num_pos:
raise TypeError('%s() takes %s %d %s (%d given)' % (
f_name, 'at most' if defaults else 'exactly', num_args,
'arguments' if num_args > 1 else 'argument', num_total))
elif num_args == 0 and num_total:
raise TypeError('%s() takes no arguments (%d given)' %
(f_name, num_total))
for arg in args:
if isinstance(arg, str) and arg in named:
if is_assigned(arg):
raise TypeError("%s() got multiple values for keyword "
"argument '%s'" % (f_name, arg))
else:
assign(arg, named.pop(arg))
if defaults: # fill in any missing values with the defaults
for arg, value in zip(args[-num_defaults:], defaults):
if not is_assigned(arg):
assign(arg, value)
if varkw:
assign(varkw, named)
elif named:
unexpected = next(iter(named))
if isinstance(unexpected, unicode):
unexpected = unexpected.encode(sys.getdefaultencoding(), 'replace')
raise TypeError("%s() got an unexpected keyword argument '%s'" %
(f_name, unexpected))
unassigned = num_args - len([arg for arg in args if is_assigned(arg)])
if unassigned:
num_required = num_args - num_defaults
raise TypeError('%s() takes %s %d %s (%d given)' % (
f_name, 'at least' if defaults else 'exactly', num_required,
'arguments' if num_required > 1 else 'argument', num_total))
return arg2value
main()
Output: 输出:
1 1000 2
{'a': 10, 'b': 1223, 'f': 'Hello World'}
There you go... I'm not responsible for any small children that get eaten by demons or something the like (or if it breaks on complicated functions). 在那里你去...我不负责任何被恶魔或类似东西吃掉的小孩子(或者如果它打破复杂的功能)。
PS: The inspect module is the pure EVIL . PS:检查模块是纯EVIL 。
Since you are trying to manipulate variables in one function, and do some job based on those variables on another function, the cleanest way to do it is having these variables to be an object's attributes. 由于您试图在一个函数中操作变量,并根据另一个函数上的这些变量执行某些工作,因此最简单的方法是将这些变量作为对象的属性。
It could be a dictionary - that could be defined inside the decorator - therefore access to it inside the decorated function would be as a "nonlocal" variable. 它可以是一个字典 - 可以在装饰器中定义 - 因此在装饰函数内部访问它将是一个“非局部”变量。 That cleans up the default parameter tuple of this dictionary, that @bgporter proposed.:
这清除了这个字典的默认参数元组,@ bgporter提出:
def eggs(self, a, b, c=None):
# nonlocal parms ## uncomment in Python 3
parms["a"] = a
...
To be even more clean, you probably should have all these parameters as attributes of the instance (self) - so that no "magical" variable has to be used inside the decorated function. 为了更加干净,你可能应该将所有这些参数作为实例的属性(self) - 这样就不必在装饰函数中使用“魔法”变量。
As for doing it "magically" without having the parameters set as attributes of certain object explicitly, nor having the decorated function to return the parameters themselves (which is also an option) - that is, to have it to work transparently with any decorated function - I can't think of a way that does not involve manipulating the bytecode of the function itself. 至于在没有明确地将参数设置为某个对象的属性的情况下“神奇地”执行它,也没有使用装饰函数来返回参数本身(这也是一个选项) - 也就是说,让它与任何装饰函数透明地工作 - 我想不出一种不涉及操作函数本身的字节码的方法。 If you can think of a way to make the wrapped function raise an exception at return time, you could trap the exception and check the execution trace.
如果您能想到一种方法来使包装函数在返回时引发异常,则可以捕获异常并检查执行跟踪。
If it is so important to do it automatically that you consider altering the function bytecode an option, feel free to ask me further. 如果自动执行此操作非常重要,您可以考虑将函数字节码更改为选项,请随时向我询问。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.