Python: Is there a way to get a local function variable from within a decorator that wraps it?

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

Working example:

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

What you're asking for doesn't really make sense.

A function's local variables don't have values all the time. Consider this function:

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

What is the value of foo's local variable y? You can't answer, the question doesn't even make sense until you call foo (and even then, not until the line y = x + 27 is executed).

And even then it's not just that y might not have a value at this moment, there could be any number of "in flight" executions of foo. There could be threads executing foo, or foo could be recursive (possibly indirectly) so that there is more than one call in progress even in a single call stack. Or foo could be a generator, so there could be many in-flight foo executions even without recursion (i.e. they're not all reachable from some outermost foo scope). So which y would you get the value of?

The value of y in foo just isn't a well-defined concept unless you're talking about within the scope of foo.


Given Python's flexibility, I'm pretty sure it's possible to do stack frame introspection and find a stack frame for foo when there is one currently live and pull out the values of its local variables at that time. This would be pretty hard (if not impossible) to do with a decorator, because (unless foo is a generator) the decorator can only add wrapper code "around" foo, which means the code controlled by the decorator runs before and after foo runs, so you only get control when foo's stack frame doesn't exist.

I'm not going to give specific pointers on exactly how to do this, because I don't know how to do it. It sounds like it's almost certainly a bad idea though, unless you're writing a debugger.


<edit> I just realized I misread the question and that you are not trying to get function attributes, but local variable values from the function. What you want to do is not possible because those local variables are not created until the function is run, and the local scope for the function is deleted as soon as the function returns or raises an exception.

I am leaving my original answer because you could potentially rewrite your function to use attributes instead of local variables and still use this decorator to effectively do what you want.

It would be helpful for you to post what you have currently attempted and some example calls with expected output if it was working correctly.</edit>

When you need a function with attributes, it is generally a good idea to use a callable class instead of a normal function definition.

Here is an example of a decorator where the wrapper is a callable class, which allows the decorator to access variables easily, because they are instance variables of the wrapper class:

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

Which results in my_func behaving like this:

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