Create decorator that can see current class method
-
02-10-2019 - |
Question
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)
Solution
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.
OTHER TIPS
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
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.