Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a decorated function access variables of the decorator

Tags:

python

I'm trying to understand how decorators work, and was wondering if a decorated function can access variables of the decorator. For example, in the following code, how do I make f1 have access to localVariable? Is that possible, and is it even a good way of doing things?

def funcDec(func):
    localVariable = "I'm a local string"
    def func2Return(*args):                                                                            
        print "Calling localVariable from decorator " + localVariable
        func(*args)                                      
        print "done with calling f1"
    return func2Return

@funcDec
def f1(x, y):
    print x + y
    print localVariable

f1(2, 3)
like image 527
iman453 Avatar asked Sep 15 '12 08:09

iman453


2 Answers

Because of Python's scoping rules, a decorated function generally can't access any variables in the decorator. However, since functions can have arbitrary attributes assigned to them, you could do something like the following in the decorator to get a similar effect (due to the same scoping rules):

def funcDec(func):
    localVariable = "I'm a local string"

    def wrapped(*args):
        print("Calling localVariable from funcDec " + localVariable)
        func(*args)
        print("done with calling f1")

    wrapped.attrib = localVariable
    return wrapped

@funcDec
def f1(x, y):
    print(x + y)
    print('f1.attrib: {!r}'.format(f1.attrib))

f1(2, 3)

Which would produce the following output:

Calling localVariable from funcDec I'm a local string
5
f1.attrib: "I'm a local string"
done with calling f1

Someone asked whether this could be applied to methods of a class: The answer is "yes", but you have to reference the method either through the class itself or the instance of it passed as the self argument. Both techniques are shown below. Using self is preferable since it makes the code independent of the name of the class it's is in.

class Test(object):
    @funcDec
    def f1(self):
        print('{}.f1() called'.format(self.__class__.__name__))
        print('self.f1.attrib: {!r}'.format(self.f1.attrib))  # Preferred.
        print('Test.f1.attrib: {!r}'.format(Test.f1.attrib))  # Also works.

print()
test = Test()
test.f1()

Output:

Calling localVariable from funcDec I'm a local string
Test.f1() called
self.f1.attrib: "I'm a local string"
Test.f1.attrib: "I'm a local string"
done with calling f1
like image 175
martineau Avatar answered Nov 06 '22 05:11

martineau


I think it helps if you keep in mind that a decorator

@deco
def f(...): ...

is just syntactic sugar for

def f(...): ...
f = deco(f)

rather than some kind of macro expansion. In Python the scope of a variable is determined statically, so for a global (module-level) function a variable that is neither passed as an argument nor assigned to will be looked up in the global namespace.

Therefore you have to pass on a local variable of func2Return() explicitly. Change the signature of f1 to f1(x, y, localvariable=None) and have the wrapper function fun2Return call it with

f1(*args, localvariable=localvariable)
like image 42
Peter Otten Avatar answered Nov 06 '22 06:11

Peter Otten