Python:有没有办法从包装它的装饰器中获取本地函数变量?

时间:2012-02-08 00:54:49

标签: python decorator

我想从包装它的装饰器中读取对象方法的本地值。 我可以从装饰器中访问函数和func_code,但似乎我可以得到的只是本地变量的名称,而不是它们的值。

有可能吗?

7 个答案:

答案 0 :(得分:12)

请参阅https://stackoverflow.com/a/4249347/224295http://code.activestate.com/recipes/577283-decorator-to-expose-local-variables-of-a-function-/

工作示例:

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

答案 1 :(得分:6)

你所要求的并没有多大意义。

函数的局部变量始终没有值。考虑这个功能:

def foo(x):
    y = x + 27
    return y

foo的局部变量y的价值是多少?您无法回答,在您致电foo之前,问题甚至没有意义(即便如此,直到行y = x + 27被执行)。

即便如此,y此时可能没有价值,foo可能会有任意数量的“飞行中”执行。可能有执行foo的线程,或foo可能是递归的(可能是间接的),因此即使在单个调用堆栈中也有多个正在进行的调用。或者foo可能是一个生成器,因此即使没有递归也可能有许多正在进行的foo执行(即,它们不能从某个最外面的foo范围内到达)。那么y你会得到什么价值?

yfoo的价值不是明确定义的概念,除非您在foo的范围内讨论。


鉴于Python的灵活性,我非常确定可以进行堆栈帧内省,并在当前有一个当前存在的时候为foo找到一个堆栈帧,然后拉出其局部变量的值。这对装饰器来说非常困难(如果不是不可能),因为(除非foo是生成器)装饰器只能在“foo周围添加包装代码,这意味着代码由装饰器在foo运行之前和之后运行,因此只有在foo的堆栈帧不存在时才能获得控制权。

我不打算详细说明如何做到这一点,因为我不知道该怎么做。听起来这几乎肯定是一个坏主意,除非你正在编写调试器。

答案 2 :(得分: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

答案 3 :(得分:0)

我曾经使用带有默认值的可选变量的把戏:

def my_decorator(fn):
    my_value = 47
    def inner():
        nonlocal my_value
        return fn(my_v=my_value)
    return inner

@my_decorator
def f1(my_v):
    print('Value from decorator', my_v)
    return my_v * 2

print(f1())
#> Value from decorator: 47
#> 94

答案 4 :(得分:0)

其中一些答案似乎是多虑了。虽然当它作为 kwarg 回到主要功能时,它在技术上并不是一个“变量”,但我相信它实现了你想要做的总体目标。

def wrapper_function(func):
    def wrap(*args, **kwargs):
        print("start of wrapper")
        kwargs["valid_json"] = "something"
        func(**kwargs)
        print("end of wrapper")
    return wrap

@wrapper_function
def foo(**kwargs):
    print("start of main function")
    print(kwargs["valid_json"])
    print("end of main function")

foo()

# start of wrapper
# start of main function
# something
# end of main function
# end of wrapper

答案 5 :(得分:-1)

不,不可能在包装函数中访问变量,因为(正如F.J.在他的编辑中指出的那样)它们在装饰器在范围内时不存在。

答案 6 :(得分:-2)

而不是摆弄函数的内部(如果修饰函数是本机的话会破坏),编写自己的包装器,如下所示:

def myDecorator(f):
  def wrapper(*args, **kwargs):
    print('Arguments to this function %r / %r' % (args, kwargs))
    return f(*args, **kwargs)
  return wrapper