Decorator for validation - inefficient?

Steven D'Aprano steven at REMOVE.THIS.cybersource.com.au
Mon Nov 3 05:06:24 CET 2008


On Sun, 02 Nov 2008 09:33:41 -0800, Bryan wrote:

> I'm coming from a .Net background, and yes, one of the reasons I did not
> consider raising exceptions was to avoid the overhead of an exception
> handler clause, which in .Net land is expensive.

Actually catching an exception in Python is expensive, so I wouldn't 
recommend you use exceptions for message-passing in time-critical code.

As I understand it, in your case you actually need to stop for user-input 
if the user's data fails the validation. If that's the case, an extra few 
milliseconds to catch an exception isn't going to matter much.



> some more thought on this:
> 
> If I were going to be checking for validity during a property setter, I
> would probably raise an exception there, because the essence of what a
> client was requesting is "set property", and an invalid value precludes
> this action from happening.
> 
> However, hoping to make client code cleaner and to avoid setter
> functions doing expensive db lookup validations, I do not validate
> during the setter, but instead defer it until the client explicitly asks
> for the validity of the business object.

Presumably if they *don't* explicitly ask, you validate anyway at some 
point?


> So the essence of the client's
> request at that point is "what are the invalid values for the object",
> and an exception should only be raised if there was something stopping
> this request from being served.  Invalid business object field values do
> not stop the functionality of the invalid() method.
>
> If I had a validation function that checked the db for a duplicate
> primary key, then the invalid() function should raise an exception if
> the db could not be contacted.

Yes!


> A client should be on the lookout for
> that type of exception, but to throw a bunch of exceptions back at a
> client who simply requested a list of things that need to be fixed seems
> heavy.
>
> We would essentially be using Exceptions as an expected return
> value of a function.  So a doc string would explain: "Returns None for a
> valid object, and Exceptions for an invalid object."
> 
> Should exceptions be an expected "return value" from a function?  Am I
> still using my .Net brain?

Because exceptions are first-class objects just like lists, ints and 
strings, there is a difference between *returning* an exception and 
*raising* an exception.

E.g.:

def build_exception(n):
    if n < 0:
        raise ValueError('unexpected negative code')
    else:
        exc = TypeError('error code #%d' % n)
        exc.foo = "More info here"
        return exc


try:
    obj = build_exception(57)
    print obj.foo
    another_obj = build_exception(-1)
    print "We never get here"
except ValueError, e:
    print "Failed with error message:", e.message



Making the exception part of your code's API is perfectly legitimate and 
I would recommend it. The docstring could say this:

"Return None for a valid object, otherwise raises InvalidDataException 
(subclass of ValueError). You can get a list of errors from the 
exception's errorlist attribute."


Here's a minimal way to generate the exception:

class InvalidDataException(ValueError):
    pass


and then in your validation code:

def validate(obj):
    print "Testing many things here"
    e = InvalidDataException("failed because of %d errors") % 4
    e.errorlist = [
        "too many questions",
        "too few answers",
        "not enough respect",
        "and your query's hair is too long (damn hippy)"]
    raise e


That's one way. There are others. Have a browse through the standard 
library or the Python docs and see how other exceptions are used.


But now that I have a better picture of your use-case, I'm leaning 
towards a completely different model. Rather than testing if the business 
object is valid, and raising an error if it isn't, you test it for 
errors, returning an empty list if there aren't any.


def check_for_errors(obj):
    errorlist = []
    print "Testing many things here"
    if too_many_questions():
        errorlist.append("too many questions")
        # and any others
    return errorlist


I've used strings as errors, but naturally they could be any object that 
makes sense for your application.



-- 
Steven



More information about the Python-list mailing list