Python:有没有办法在包装函数的装饰器中获取局部变量?

23 投票
7 回答
13878 浏览
提问于 2025-04-17 12:33

我想从一个装饰器中读取一个对象方法的局部变量值。虽然我可以在装饰器里访问到这个函数和它的代码,但似乎我只能得到局部变量的名字,而不能获取它们的实际值。

这样做可能吗?

7 个回答

3

<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
8

你问的问题其实不太合理。

一个函数的局部变量并不是一直都有值。想想这个函数:

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 的栈帧不存在时获得控制权。

我不会给出具体的指引,因为我也不知道怎么做。不过,这听起来几乎肯定是个坏主意,除非你是在写一个调试器。

18

请查看 这个链接这个链接

下面是一个工作示例:

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

撰写回答