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


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

可能吗?


9927
2018-02-08 00:54


起源



答案:


看到 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

9
2018-02-08 02:29



这样可行。我首先同意这是一个坏主意,但我有兴趣看看它是否可行。 - Harel
很棒的技巧。我正在制作一个调试器,它必须偷偷地从装饰功能中抓取内部构件,因此它是合法有用的。需要注意的一点是,tracer()不仅在返回装饰函数时被调用,而且还在从该函数内调用的所有函数的返回时调用。由于修饰函数总是具有最后一个返回(因此将_locals设置为last),因此在这种情况下无关紧要。 - Peter


答案:


看到 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

9
2018-02-08 02:29



这样可行。我首先同意这是一个坏主意,但我有兴趣看看它是否可行。 - Harel
很棒的技巧。我正在制作一个调试器,它必须偷偷地从装饰功能中抓取内部构件,因此它是合法有用的。需要注意的一点是,tracer()不仅在返回装饰函数时被调用,而且还在从该函数内调用的所有函数的返回时调用。由于修饰函数总是具有最后一个返回(因此将_locals设置为last),因此在这种情况下无关紧要。 - Peter


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

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

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

什么是价值 foo的局部变量 y?你无法回答,这个问题在你打电话之前甚至没有意义 foo (即便如此,直到排队 y = x + 27 被执行)。

即便如此,它不仅仅是那样 y 此刻可能没有价值,可能会有 任何数字 “飞行中”执行的 foo。可能有线程正在执行 foo, 要么 foo 可以是递归的(可能是间接的),以便即使在单个调用堆栈中也有多个正在进行的调用。要么 foo 可能是一个发电机,因此可能有许多飞行中 foo 甚至没有递归的执行(即它们不是从一些最外层可以到达的 foo 范围)。那么哪个 y 你会得到它的价值吗?

的价值 y 在 foo 只是不是一个定义明确的概念,除非你在讨论的范围内 foo


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

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


6
2018-02-08 01:45





<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
2018-02-08 01:23





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


-1
2018-02-08 01:41





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

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

-2
2018-02-08 01:02