I'm trying to make some validations for the class methods of a class using one of the parameters used when calling them.
To do this, I'm using a decorator for the class that will apply a decorator to the required methods, which will perform a validation function using one of the parameters in the function.
This all works well for the base class (for this example I will call it Parent
).
However, if I make another class which inherits Parent
, (for this example I will call it Child
), the inherited decorated classmethod no longer behaves normally.
The cls
parameter inside the classmethod for the Child
class is not Child
as expected, but is Parent
instead.
Taking the following example
import inspect
def is_number(word):
if word.isdigit():
print('Validation passed')
else:
raise Exception('Validation failed')
class ClassDecorator(object):
def __init__(self, *args):
self.validators = args
def __decorateMethod(self):
def wrapped(method):
def wrapper(cls, word, *args, **kwargs):
for validator in self.validators:
validator(word)
return method(word, *args, **kwargs)
return wrapper
return wrapped
def __call__(self, cls):
for name, method in inspect.getmembers(cls):
if name == 'shout':
decoratedMethod = self.__decorateMethod()(method)
setattr(cls, name, classmethod(decoratedMethod))
return cls
@ClassDecorator(is_number)
class Parent(object):
@classmethod
def shout(cls, word):
print('{} is shouting {}'.format(cls, word))
@classmethod
def say(cls):
print('{} is talking'.format(cls))
class Child(Parent):
pass
Parent.shout('123')
Child.shout('321')
Will result in the following output:
Validation passed
<class '__main__.Parent'> is shouting 123
Validation passed
<class '__main__.Parent'> is shouting 321
My questions are:
Child
get called with Parent
as clsP.S.: I've tried this on both Python 2.7.10 and Python 3.5.2 and have gotten the same behaviour
You are decorating the bound class method; it is this object that holds on to Parent
and passes it into the original shout
function when called; whatever cls
is bound to in your wrapper()
method is not passed in and ignored.
Unwrap classmethods first, you can get to the underlying function object with the __func__
attribute:
def __call__(self, cls):
for name, method in inspect.getmembers(cls):
if name == 'shout':
decoratedMethod = self.__decorateMethod()(method.__func__)
setattr(cls, name, classmethod(decoratedMethod))
return cls
You now have to take into account that your wrapper is handling an unbound function too, so pass on the cls
argument or manually bind:
# pass in cls explicitly:
return method(cls, word, *args, **kwargs)
# or bind the descriptor manually:
return method.__get__(cls)(word, *args, **kwargs)