【问题标题】:Python: Is there a way to get a local function variable from within a decorator that wraps it?Python:有没有办法从包装它的装饰器中获取局部函数变量?
【发布时间】:2012-02-29 11:30:22
【问题描述】:

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

有可能吗?

【问题讨论】:

    标签: python decorator


    【解决方案1】:

    其中一些答案似乎是多虑了。虽然从技术上讲,当它作为 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
    

    【讨论】:

      【解决方案2】:

      我曾经对具有默认值的可选变量使用技巧:

      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
      

      【讨论】:

        【解决方案3】:

        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
        

        【讨论】:

        • 那行得通。我确实同意这是一个坏主意,但我有兴趣看看它是否可能。
        • 绝招。我正在制作一个调试器,它必须偷偷地从装饰函数中获取内部结构,因此它是合法有用的。需要注意的一点是,tracer() 不仅在装饰函数的返回上被调用,而且在从该函数内调用的所有函数的返回上都被调用。由于修饰函数总是有最后一个返回值,(因此最后设置 _locals)在这种情况下并不重要。
        【解决方案4】:

        你所要求的并不真正有意义。

        函数的局部变量并不总是有值。考虑这个函数:

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

        我不会给出具体的具体方法,因为我不知道该怎么做。听起来这几乎可以肯定是个坏主意,除非您正在编写调试器。

        【讨论】:

          【解决方案5】:

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

          【讨论】:

            【解决方案6】:

            <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
            

            【讨论】:

              【解决方案7】:

              不要摆弄函数的内部结构(如果修饰的函数是原生的,这将破坏),编写自己的包装器,如下所示:

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2020-06-15
                • 2011-10-30
                • 1970-01-01
                • 2012-05-11
                • 2019-06-16
                • 2020-05-02
                • 2017-12-24
                相关资源
                最近更新 更多