Reputation: 11879
I am not sure my question has a concrete answer, but anyway. I am writing a function with a lot of parameters, each one can be either None
or have a limited range of values. Since I don't trust the user to give me good input, I have to check each parameter for its type, and if it's in the right type (or None
), then I would like to see if it's in the right range. This mean I have a lot of code like this:
# size
if isinstance(size, str):
if size in range(4):
self.data[uid]['size'] = int(size)
else:
warnings.warn("ID %s: illegal size %s" % (uid, size))
self.data[uid]['size'] = None
elif size == None:
self.data[uid]['size'] = None
else:
warnings.warn("ID %s: illegal size %s" % (uid, str(size)))
self.data[uid]['size'] = None
etc. As this is becoming repetitive, I was wondering if there might be a library that would automate this, throw exceptions/warnings and reduce code redundancy.
Thanks
Upvotes: 1
Views: 549
Reputation: 116247
I would rewrite this:
# size
if isinstance(size, str):
if size in range(4):
self.data[uid]['size'] = int(size)
else:
warnings.warn("ID %s: illegal size %s" % (uid, size))
self.data[uid]['size'] = None
elif size == None:
self.data[uid]['size'] = None
else:
warnings.warn("ID %s: illegal size %s" % (uid, str(size)))
self.data[uid]['size'] = None
like this:
if size in ["0", "1", "2", "3"]: # alternative: if size in map(str, range(4)):
self.data[uid]['size'] = int(size)
else:
if size != None:
warnings.warn("ID %s: illegal size %s" % (uid, size))
self.data[uid]['size'] = None
What I sincerely don't like is the use of isinstance(size, str)
(explicit typechecking is generally frowned upon in Python since it easily breaks ducktyping).
This is the reason why you won't easily find a library in Python to automate typechecking: it goes against the core intent of the language.
Upvotes: 1
Reputation: 107666
I was wondering if there might be a library that would automate this, throw exceptions/warnings and reduce code redundancy.
I use formencode for stuff like this. It appears to be only for parsing HTML forms, but it will happily parse and validate anything you pass to it. You define schema classes that validate all input at once.
Upvotes: 1
Reputation: 154574
I would agree with the above: assume the size
is of the right type, let an exception be raised (or an error be returned) if it isn't.
There is a useful pattern, though, when you're dealing with input that might raise an exception: wrapping any exceptions raised by the input so they include the input. For example, so you'll get:
ParseError: while parsing 'number = foo': ValueError: invalid literal for int() with base 10: 'foo'
The code looks something like this:
try:
parse(input)
catch Exception, e:
raise ParseError("while parsing %r: %r" %(input, e)), None, sys.exc_info()[2]
The third argument to raise
will use the original traceback, so the stack trace will point you to the line which actually caused the error (eg, size = int(value)
) rather than the call to raise
.
Upvotes: 1
Reputation: 11448
If your project is a library and your "user" is another developer, don't do this at all. At most, replace your tests with an assertion:
assert 0 <= int(size) <= 4, "size must be between 0 and 4"
This way, when the user provides bad input, they'll hear about it straight away - then it becomes their responsibility to get it right.
If your project is an application and your "user" is my grandmother, you need to do your own verification: crashing out is not an acceptable response. However, in this case you should know more about the possible input (for example, it's come from a text box, therefore it can only be a string).
My suggestion would be to code your utility functions as above and perform validation separately (ie. UI layer). It is very rare that a utility/library function should validate and override (even with a warning) the value: it needs to prevent the caller/user from getting any further until they have gotten their side of things right.
Upvotes: 0