0

I have a child class method I want to conditionally short circuit. What I'm trying to do is something like this except I want to put the validation logic into the base class.

class BaseClass(object):
    def getvalue(self):
        return True
    def validate(self):
        validated = self.getvalue()
        return validated

class ExtendedClass1(BaseClass):
    def do_some_work(self):
        validated = self.validate()
        if not validated:
            print "Not validated."
            return
        print "Things are validated if the method got this far.", validated

class ExtendedClass2(BaseClass):
    def do_some_work(self):
        validated = self.validate()
        if not validated:
            print "Not validated."
            return
        print "Things are validated if the method got this far.", validated

class ExtendedClass3(BaseClass):
    def do_some_work(self):
        print "This one doesn't require validation."

work1 = ExtendedClass1()
work1.do_some_work()

work2 = ExtendedClass2()
work2.do_some_work()

work3 = ExtendedClass3()
work3.do_some_work()

Following this example I was able to convert some of the repeated code into a decorator pattern.

class BaseClass(object):

    def validate(input_function):
        def wrapper(*args,**kwargs):
            validated = True
            if not validated:
                print "Not validated."
                return
            input_function(*args, **kwargs)
        return wrapper

    validate = staticmethod(validate)

class ExtendedClass1(BaseClass):
    @BaseClass.validate
    def do_some_work(self):
        print "Things are validated if the method got this far."

class ExtendedClass2(BaseClass):
    @BaseClass.validate
    def do_some_work(self):
        print "Things are validated if the method got this far."

class ExtendedClass3(BaseClass):
    def do_some_work(self):
        print "This one doesn't require validation."

work1 = ExtendedClass1()
work1.do_some_work()

work2 = ExtendedClass2()
work2.do_some_work()

work3 = ExtendedClass3()
work3.do_some_work()

However, I need to call a method of the base class in the decorator to do the validation work, and retrieve the value of (validated) in the child class. Following this example here, I modified the decorator in an attempt to let it call self.getvalue(). At this point it doesn't error out, but it doesn't work either because self.getvalue() doesn't return True. This is starting to seem like more trouble than it's worth, but now I'm curious as to whether or not it's possible.

class BaseClass(object):

    def getvalue(self):
        return True

    def validate(self):
        def wrap(input_function):
            def wrapper(*args,**kwargs):
                validated = self.getvalue()
                if not validated:
                    print "Not validated."
                    return
                input_function(*args, **kwargs)
            return wrapper
        return wrap

    validate = staticmethod(validate)

class ExtendedClass1(BaseClass):
    @BaseClass.validate
    def do_some_work(self):
        print "Things are validated if the method got this far."#, validated

class ExtendedClass2(BaseClass):
    @BaseClass.validate
    def do_some_work(self):
        print "Things are validated if the method got this far."#, validated

class ExtendedClass3(BaseClass):
    def do_some_work(self):
        print "This one doesn't require validation."

work1 = ExtendedClass1()
work1.do_some_work()

work2 = ExtendedClass2()
work2.do_some_work()

work3 = ExtendedClass3()
work3.do_some_work()

Is it possible to set an attribute with the decorator and then retrieve it later?

                ...
                self.validated = True
                if not self.validated:
                    print "Not validated."
                    return
                ...
print work1.validated
                ...

AttributeError: 'ExtendedClass1' object has no attribute 'validated'

Essentially, I want to turn this:

class ExtendedClass1(BaseClass):
    def do_some_work(self):
        validated = self.validate()
        if not validated:
            print "Not validated."
            return
        print "Things are validated if the method got this far.", validated

Into this:

class ExtendedClass1(BaseClass):
    @BaseClass.validate
    def do_some_work(self):
        print "Things are validated if the method got this far.", validated

Using the suggestion posted by Zaur Nasibov, this example satisfies my use case. I'm still interested to know if @validate can be implemented as a method instead of a stand alone function, but this gets the job done.

class BaseClass(object):
    def getvalue(self):
        return True

def validate(func):
    def wrapped(self, *args, **kwargs):
        validated = self.getvalue()
        self.validated = validated
        if not validated:
            print "Not validated."
            return
        func(self, *args, **kwargs)
    return wrapped

class ExtendedClass1(BaseClass):
    @validate
    def do_some_work(self,input):
        print "Things are validated if the method got this far.", self.validated, input

class ExtendedClass2(BaseClass):
    @validate
    def do_some_work(self):
        print "Things are validated if the method got this far.", self.validated

class ExtendedClass3(BaseClass):
    def do_some_work(self):
        print "This one doesn't require validation."#, self.validated

work1 = ExtendedClass1()
work1.do_some_work(input="some text")

work2 = ExtendedClass2()
work2.do_some_work()

work3 = ExtendedClass3()
work3.do_some_work()
Community
  • 1
  • 1
tponthieux
  • 1,502
  • 5
  • 18
  • 30

2 Answers2

1

@tponthieux, what you can do is setting the attribute of the called function (method) and then retrieving it:

Simple example (updated):

def validate(func):    
    def wrapped(self, *args, **kwargs):
        self.valid = True
        func(self, *args, **kwargs)
    return wrapped

class TestClass(object):
    @validate
    def do_some_work(self):
        print "some work done"

tc = TestClass()
tc.do_some_work()
print tc.valid
Zaur Nasibov
  • 22,280
  • 12
  • 56
  • 83
  • This looks promising but I'm not sure how to let the decorator code access the getvalue() method of the base class. Does anyone want to try to work this technique into the question example? – tponthieux May 30 '11 at 16:54
  • Allright, another example :) But I think this one will definitely help you. – Zaur Nasibov May 30 '11 at 17:07
  • You never call the wrapped `func`, you only return it from the wrapper. That's not going to give you the expected results ;-) – Martijn Pieters May 30 '11 at 17:20
  • You are getting confused about `*args` and `**kwargs` there, I think. You'll need to pass them to do_some_work *with* those attributes. – Martijn Pieters May 30 '11 at 17:37
  • I am not entirely sure your answer is what the OP is asking for, but at least the code is now syntactically correct. :-) – Martijn Pieters May 30 '11 at 18:20
1

How about only calling the decorated method if it has been validated? You can pass the return value of the validate method along if you like:

class BaseClass(object):
    def getvalue(self):
        return True

    def validate(input_function):
        def wrapper(self, *args, **kwargs):
            self.validated = self.getvalue()
            if not self.validated:
                print "Not validated."
                return
            input_function(self, validated=self.validated, *args, **kwargs)
        return wrapper

    validate = staticmethod(validate)

class ExtendedClass1(BaseClass):
    @BaseClass.validate
    def do_some_work(self, validated=None):
        print "Things are validated if the method got this far.", validated

class ExtendedClass2(BaseClass):
    @BaseClass.validate
    def do_some_work(self, validated=None):
        print "Things are validated if the method got this far.", validated

class ExtendedClass3(BaseClass):
    def do_some_work(self):
        print "This one doesn't require validation."

work1 = ExtendedClass1()
work1.do_some_work()

work2 = ExtendedClass2()
work2.do_some_work()

work3 = ExtendedClass3()
work3.do_some_work()

The key here is adding self to the wrapper function. What happens is that your decorated functions do not get bound to the instance (and become methods), but the function returned by the decorator (wrapper in the above example) get's bound instead. So this function will get the self (the instance) parameter passed in when called! It's important to remember that what a @decorator does is simply call decorator passing in the function you are decorating, and then replace the function you are decorating with whatever the decorator returned. In your example this is wrapper, and to the class, there is no difference between that and the original function before decorating.

In the above example, I declared self explicitly. If we hadn't, we could also have just taken it from args:

def validate(input_function):
    def wrapper(*args, **kwargs):
        print "args[0] is now the instance (conventionally called 'self')", args[0]
        self = args[0]
        self.validated = self.getvalue()
        if not self.validated:
            print "Not validated."
            return
        input_function(validated=self.validated, *args, **kwargs)

Also note that we pass an extra keyword argument to the wrapped method, named validated. This is entirely optional, you can just drop the validated=self.validated and validated=None parts from the example.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • I wasn't the downvoter, but the negative vote may be because the answer doesn't run as posted. One thing to note is that @BaseClass.validate has to call a method described in the base class outside of the decorator method. `validated = self.getvalue()` I'll edit the first code block of the question to make that a little more clear. – tponthieux May 30 '11 at 19:31
  • Martijn, I apologize for downvoting with no reason. There is absolutely nothing wrong with your answer, in fact I also learned from it. Could you please edit it just a bit, so I could cancel that "-1". Thank you. – Zaur Nasibov May 30 '11 at 19:35
  • Ah, I first read this as "However, I need to call a method of the base class in the decorator to do the validation work, and retrieve the value of (validated) in the child class." but later on you want to set self.validated. I'll update. – Martijn Pieters May 30 '11 at 20:05
  • @tponthieux: updated to include the `def getvalue(self)` method from the OP, which code adjusted to call that instead of the fictional `def validate(self)` method. The code example executes as is now. – Martijn Pieters May 30 '11 at 20:26