Python:有没有办法在包装函数的装饰器中获取局部变量?
我想从一个装饰器中读取一个对象方法的局部变量值。虽然我可以在装饰器里访问到这个函数和它的代码,但似乎我只能得到局部变量的名字,而不能获取它们的实际值。
这样做可能吗?
7 个回答
<edit>
我刚意识到我误解了问题,你并不是想获取函数的属性,而是想从函数中获取局部变量的值。你想做的事情是不可能的,因为这些局部变量只有在函数运行时才会被创建,而且一旦函数返回或抛出异常,局部作用域就会被删除。
我保留我最初的回答,因为你可以考虑重写你的函数,使用属性而不是局部变量,并且仍然可以使用这个装饰器来有效地实现你想要的功能。
如果你能分享一下你目前尝试过的内容,以及一些示例调用和预期输出(如果它能正常工作的话),那会很有帮助。</edit>
当你需要一个带有属性的函数时,通常使用可调用类会比普通的函数定义更好。
下面是一个装饰器的例子,其中的包装器是一个可调用类,这样装饰器就能轻松访问变量,因为它们是包装类的实例变量:
def deco(func):
class Wrapper(object):
def __init__(self):
self.foo = None
def __call__(self, *args):
print 'old foo:', self.foo
result = func(*args)
print 'new foo:', self.foo
return result
return Wrapper()
@deco
def my_func(new_foo):
my_func.foo = new_foo
这使得 my_func
的行为如下:
>>> my_func('test')
old foo: None
new foo: test
>>> my_func.foo
'test'
>>> my_func(42)
old foo: test
new foo: 42
>>> my_func.foo
42
你问的问题其实不太合理。
一个函数的局部变量并不是一直都有值。想想这个函数:
def foo(x):
y = x + 27
return y
那么,foo
的局部变量 y
的值是什么呢?你无法回答这个问题,因为在你调用 foo
之前,这个问题根本没有意义(而且即使调用了,也要等到执行到 y = x + 27
这一行才能知道)。
而且,即使在那时,y
可能在此刻没有值,还有可能有很多个正在执行的 foo
。可能有多个线程在执行 foo
,或者 foo
可能是递归的(可能是间接递归),这样即使在一个调用栈中,也会有多个调用在进行中。或者 foo
可能是一个生成器,这样即使没有递归,也可能有很多个正在执行的 foo
(也就是说,它们并不都是从某个最外层的 foo
范围中可以到达的)。那么你想知道的 y
的值是哪一个呢?
在 foo
中,y
的值并不是一个明确的概念,除非你是在讨论 foo
的作用域内。
考虑到 Python 的灵活性,我相信可以进行栈帧的检查,找到一个当前活跃的 foo
的栈帧,并提取出当时它的局部变量的值。不过,这样做会很困难(甚至不可能),尤其是用装饰器来实现,因为(除非 foo
是一个生成器)装饰器只能在 foo
的“外面”添加包装代码,这意味着装饰器控制的代码在 foo
执行之前和之后运行,所以你只能在 foo
的栈帧不存在时获得控制权。
我不会给出具体的指引,因为我也不知道怎么做。不过,这听起来几乎肯定是个坏主意,除非你是在写一个调试器。
下面是一个工作示例:
import sys
class persistent_locals(object):
def __init__(self, func):
self._locals = {}
self.func = func
def __call__(self, *args, **kwargs):
def tracer(frame, event, arg):
if event=='return':
self._locals = frame.f_locals.copy()
# tracer is activated on next call, return or exception
sys.setprofile(tracer)
try:
# trace the function call
res = self.func(*args, **kwargs)
finally:
# disable tracer and replace with old one
sys.setprofile(None)
return res
def clear_locals(self):
self._locals = {}
@property
def locals(self):
return self._locals
@persistent_locals
def func():
local1 = 1
local2 = 2
func()
print func.locals