21

I'd like to read an object method's local value from a decorator that wraps it. I've got access to the function and func_code from within the decorator but it seems all I can get out of it is the names of the local vars, but not their values.

Is it possible?

Harel
  • 1,989
  • 3
  • 26
  • 44

7 Answers7

17

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
Community
  • 1
  • 1
philofinfinitejest
  • 3,987
  • 1
  • 24
  • 22
  • That works. I do agree its a bad idea in the first place though but I was interested to see if its possible. – Harel Feb 09 '12 at 10:14
  • 2
    Great trick. I'm making a debugger that has to sneakily grab internals from a decorated function and for that it's legitimately useful. One thing to note is that tracer() is called not just on the return of the decorated function, but also on the returns of all the functions called from within that function. Since the decorated function always has the last return, (and therefore sets _locals last) it doesn't matter in this case. – Peter Feb 15 '15 at 08:36
8

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.

Ben
  • 68,572
  • 20
  • 126
  • 174
3

<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
Andrew Clark
  • 202,379
  • 35
  • 273
  • 306
0

I once used trick with optional variable that has default value:

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
0

Some of these answers seem to be overthinking. Although it's not technically a 'variable' when it comes back to the main function as a kwarg, I believe it's achieving the overall goal of what you want to do.

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
Chris A
  • 863
  • 1
  • 10
  • 31
-1

No, it is not possible to access variables inside the wrapped function, because (as F.J. pointed out in his edit) they don't exist at the time the decorator is in scope.

mattbornski
  • 11,895
  • 4
  • 31
  • 25
-2

Instead of fiddling around with the function's internals (which will break if the decorated function is native), write your own wrapper, like this:

def myDecorator(f):
  def wrapper(*args, **kwargs):
    print('Arguments to this function %r / %r' % (args, kwargs))
    return f(*args, **kwargs)
  return wrapper
phihag
  • 278,196
  • 72
  • 453
  • 469