views:

92

answers:

3

I find myself writing the same argument checking code all the time for number-crunching:

def myfun(a, b):
    if a < 0:
        raise ValueError('a cannot be < 0 (was a=%s)' % a)
    # more if.. raise exception stuff here ...
    return a + b

Is there a better way? I was told not to use 'assert' for these things (though I don't see the problem, apart from not knowing the value of the variable that caused the error).

edit: To clarify, the arguments are usually just numbers and the error checking conditions can be complex, non-trivial and will not necessarily lead to an exception later, but simply to a wrong result. (unstable algorithms, meaningless solutions etc)

A: 

You don't want to use assert because your code can be run (and is by default on some systems) in such a way that assert lines are not checked and do not raise errors (-O command line flag).

If you're using a lot of variables that are all supposed to have those same properties, why not subclass whatever type you're using and add that check to the class itself? Then when you use your new class, you know you never have an invalid value, and don't have to go checking for it all over the place.

Paul McMillan
+4  A: 

assert gets optimized away if you run with python -O (modest optimizations, but sometimes nice to have). One preferable alternative if you have patterns that often repeat may be to use decorators -- great way to factor out repetition. E.g., say you have a zillion functions that must be called with arguments by-position (not by-keyword) and must have their first arguments positive; then...:

def firstargpos(f):
  def wrapper(first, *args):
    if first < 0:
      raise ValueError(whateveryouwish)
    return f(first, *args)
  return wrapper

then you say something like:

@firstargpos def myfun(a, b): ...

and the checks are performed in the decorators (or rather the wrapper closure it returns) once and for all. So, the only tricky part is figuring out exactly what checks your functions need and how best to call the decorator(s) to express those (hard to say, without seeing the set of functions you're defining and the set of checks each needs!-). Remember, DRY ("Don't Repeat Yourself") is close to the top spot among guiding principles in software development, and Python has reasonable support to allow you to implement DRY and avoid boilerplatey, repetitious code!-)

Alex Martelli
I suppose his use case makes a difference, but are wrappers preferred over my suggestion of subclassing his data type?
Paul McMillan
Forcing every caller to use the "subclassed" data type is highly invasive of every caller and reduces the functions' reusability to a parlous extent. Decorators are just another way to express the set of checks each function needs (avoiding boilerplate and repetition) and are totally trasparent to callers, which is a very good thing.
Alex Martelli
Fair enough. I'd assumed he was sanitizing input, and then passing it around to and from all his functions, and so a subclassed data type would keep him from having to re-run that check every time.
Paul McMillan
No, I am sanitising the input of each function separately, but the decorator is nice to keep in mind for the future, cheers
MarkkS
A: 

I'm not sure if this will answer your question, but it strikes me that checking a lot of arguments at the start of a function isn't very pythonic.

What I mean by this is that it is the assumption of most pythonistas that we are all consenting adults, and we trust each other not to do something stupid. Here's how I'd write your example:

def myfun(a, b):
    '''a cannot be < 0'''
    return a + b

This has three distinct advantages. First off, it's concise, there's really no extra code doing anything unrelated to what you're actually trying to get done. Second, it puts the information exactly where it belongs, in help(myfun), where pythonistas are expected to look for usage notes. Finally, is a non-positive value for a really an error? Although you might think so, unless something definitely will break if a is zero (here it probably wont), then maybe letting it slip through and cause an error up the call stream is wiser. after all, if a + b is in error, it raises an exception which gets passed up the call stack and behavior is still pretty much the same.

TokenMacGuy
I disagree. If your function is more complex than a+b, an error of the negative a nature may very well evaluate properly but return improper results. Think bank account deposits. I agree you should only check your inputs for validity at the appropriate places, but assuming that python will throw errors for wrong data is dangerous. "Explicit is better than Implicit"!
Paul McMillan
@Paul McMillan: In the context of bank transactions, the 'consenting adults' assumption is probably also invalid. Rather, I would prefer the institution I bank with to assume everyone it ever interacts with to be children or criminals or both.
TokenMacGuy
I disagree. If you know in advance that a negative argument is wrong, check at the call to the function and raise an exception right away. It might be wrong and not lead to an exception; it might be wrong and lead to a strange, perplexing exception; it might be wrong and break something that, many thousands of lines of code later, raises an exception and is thus very perplexing indeed. (That "consenting adults" thing was, I believe, originally in reference to the complaint that there is no way to prevent people from introspecting private variables, and in that context I agree with it.)
steveha