views:

69

answers:

3

Can you create a decorator inside a class that will see the classes methods and variables?

The decorator here doesnt see: self.longcondition()

class Foo:
    def __init__(self, name):
        self.name = name

    # decorator that will see the self.longcondition ???
    class canRun(object):
            def __init__(self, f):
                self.f = f

            def __call__(self, *args):
                if self.longcondition(): # <-------- ???
                    self.f(*args)

    # this is supposed to be a very long condition :)
    def longcondition(self):
        return isinstance(self.name, str)

    @canRun # <------
    def run(self, times):
        for i in xrange(times):
            print "%s. run... %s" % (i, self.name)
+1  A: 

My previous answer was made in haste. If you're wanting to write a decorator you should really use wraps from the functools module. It takes care of the hard stuff for you.

A proper way to define the canRun decorator is:

from functools import wraps
def canRun(f):
  @wraps(f)
  def wrapper(instance, *args):
    if instance.longcondition():
      return f(instance, *args)
  return wrapper

The canRun function should be defined outside of the class.

Geoff Reedy
this doesn't work and there is no reason to expect that it should. You want a descriptor. See my answer.
aaronasterling
@aaronasterling, you're absolutely right. I based my answer on previous experience using functools.wraps without testing to see if it worked. I've updated my answer to use it
Geoff Reedy
+4  A: 

There's no real need to implement this decorator as a class, and there's no need to implement it inside the definition of the Foo class. The following will suffice:

def canRun(meth):
    def decorated_meth(self, *args, **kwargs):
        if self.longcondition():
            print 'Can run'
            return meth(self, *args, **kwargs)
        else:
            print 'Cannot run'
            return None
    return decorated_meth

Using that decorator seems to work:

>>> Foo('hello').run(5)
Can run
0. run... hello
1. run... hello
2. run... hello
3. run... hello
4. run... hello
>>> Foo(123).run(5)
Cannot run
Will McCutchen
Note that this implementation assumes that it will be decorating bound methods, and so could not be used to decorate arbitrary functions.
Will McCutchen
+1  A: 

You can have it be a class but you need to use the descriptor protocol

 import types

 class canRun(object):
    def __init__(self, f):
        self.f = f
        self.o = object  # <-- What the hell is this about? 

    def __call__(self, *args):
        if self.longcondition():
            self.f(*args)

    def __get__(self, instance, owner):
         return types.MethodType(self, instance)

You always need to use a descriptor when you want to decorate class methods with a class instance using the __call__ method. The reason for this is that there will only be one self passed in which refers to the instance of the decorating class and not the instance of the decorated method.

aaronasterling