views:

232

answers:

4

How can I use a class instance variable as an argument for a method decorator in Python? The following is a minimal example shows what I'm trying to do. It obviously fails as the decorator function does not have access to the reference to the instance and I have no idea how to get access to the reference from the decorator.

def decorator1(arg1):
    def wrapper(function):
        print "decorator argument: %s" % arg1
        return function
    return wrapper

class Foo(object):
    def __init__(self, arg1):
        self.var1 = arg1

    @decorator1(self.var1)
    def method1(self):
        print "method1"

foo = Foo("abc")
foo.method1()
A: 

The decorator is executed when the class is defined, so you can't pass an instance variable to it.

UncleZeiv
Concerning your second sentence: The decorator is what the part after "@" evaluates to, so the actual decorator in this case is (supposed to be) "wrapper", not "decorator1". And "wrapper" does take a function.
balpha
Actually you can pass a runtime variable to it, just you cannot pass "self" (and its data members) because, as you pointed out, decorators are executed when the statement "def" is executed (thus, when no "self" exists)
happy_emi
@balpha: you are right, I got confused by the syntax myself. Edited.
UncleZeiv
@happy_emi: right, I meant an instance variable. Edited.
UncleZeiv
+2  A: 

It's not going to work; the decorator is called during class creation time, which is long before an instance is created (if that ever happens). So if your "decorator" needs the instance, you have to do the "decorating" at instantiation time:

def get_decorator(arg1):
    def my_decorator(function):
        print "get_decorator argument: %s" % arg1
        return function
    return my_decorator

class Foo(object):
    def __init__(self, arg1):
        self.var1 = arg1
        self.method1 = get_decorator(self.var1)(self.method1)

    def method1(self):
        print "method1"

foo = Foo("abc")
foo.method1()

Note that I changed the function names according to their meanings; the actual "decorator", i.e. the function that (potentially) modifies the method, is wrapper in your case, not decorator1.

balpha
Out of curiosity: if you call the decorator directly like you do here, what's the point of the two nested functions?
UncleZeiv
The decorator depends on a parameter passed to it, i.e. you have a different decorator every time. I agree it does seem pretty pathological in this example code, but I suppose the OP has some actual use for this.
balpha
I was thinking about using the decorator for file locking, the argument passed to it is the filename which is an attribute of the instance, it seemed a nice way to avoid wrapping methods in try: ... finally blocks (I cannot use the new with statement as I have to use Python 2.4). Thanks for the explanation, I am not sure anymore if using decorators is the best solution to this problem.
Although I will stay with using wrappers this answer show how use decorators and why their usage is probably not appropriate here.
+1  A: 

Your “warper” function is actually a decorator, rather than a warper. Your “decorator1” function is a decorator constructor. If you want to have access to self.var1 in runtime you have to make a warper not decorator:

def decorator(function):
  def wrapper(self,*args,**kwargs):
    print "Doing something with self.var1==%s" % self.var1
    return function(self,*args,**kwargs)
  return wrapper

class Foo(object):
  def __init__(self, arg1):
    self.var1 = arg1

  @decorator
  def method1(self):
    print "method1"

foo = Foo("abc")
foo.method1()

If you want to have more generic decorator, it's better idea to declare a callable class:

class decorator:
  def __init__(self,varname):
      self.varname = varname
  def __call__(self,function):
    varname=self.varname
    def wrapper(self,*args,**kwargs):
      print "Doing something with self.%s==%s" % (varname,getattr(self,varname))
      return function(self,*args,**kwargs)
    return wrapper

Using:

  @decorator("var1")
lionbest
A: 

Here's how we used to do this in the olden days.

class Foo(object):
    def __init__(self, arg1):
        self.var1 = arg1

    def method1(self):
        self.lock()
        try:
            self.do_method1()
        except Exception:
            pass # Might want to log this
        finally:
            self.unlock()

    def do_method1(self):
        print "method1"

    def lock(self):
        print "locking: %s" % self.arg1

    def unlock(self):
        print "unlocking: %s" % self.arg1

Now, a subclass only needs to o override do_method1 to get the benefits of the "wrapping". Done the old way, without any with statement.

Yes, it's long-winded. However, it doesn't involve any magic, either.

S.Lott
Thanks, but this is basically what I already have, I thought decorator usage might be more elegant here but it's apparently not the case.
It can't be more elegant because it isn't cross-cutting functionality (like a log) that's largely independent of object state.
S.Lott