Exception as the primary error handling mechanism?

Steven D'Aprano steve at REMOVE-THIS-cybersource.com.au
Tue Jan 5 09:02:50 EST 2010


On Tue, 05 Jan 2010 13:06:20 +0000, r0g wrote:

> Dave Angel wrote:
>> 
>> 
>> r0g wrote:
>>> <snip>
>>>
>>> Maybe, although I recently learned on here that one can't rely on
>>> assert
>>>  statements in production code, their intended use is to aid debugging
>>> and testing really.
>>>
>>>
>> Hopefully, what you learned is that you can't use assert() in
>> production code to validate user data.  It's fine to use it to validate
>> program logic, because that shouldn't still need testing in production.
>> 
>> <snip>
>> 
>> DaveA
> 
> 
> 
> Well maybe I didn't quite get it then, could you explain a bit further?
> 
> My understanding was that asserts aren't executed at all if python is
> started with the -O or -OO option, 

Correct.


> or run through an optimizer. 

I don't know what you mean by that.


> If
> that's the case how can you expect it to validate anything at all in
> production? 

The asserts still operate so long as you don't use the -O switch.

> Do you mean for debugging in situ or something? Could you
> maybe give me an example scenario to illustrate your point?


There are at least two sorts of validation that you will generally need 
to perform: validating user data, and validating your program logic.

You *always* need to validate user data (contents of user-editable config 
files, command line arguments, data files, text they type into fields, 
etc.) because you have no control over what they put into that. So you 
shouldn't use assert for validating user data except for quick-and-dirty 
scripts you intend to use once and throw away.

Program logic, on the other hand, theoretically shouldn't need to be 
validated at all, because we, the programmers, are very clever and 
naturally never make mistakes. Since we never make mistakes, any logic 
validation we do is pointless and a waste of time, and therefore we 
should be able to optimise it away to save time.

*cough*

Since in reality we're not that clever and do make mistakes, we actually 
do want to do some such program validation, but with the option to 
optimise it away. Hence the assert statement.

So, a totally made-up example:


def function(x, y):
    if x < 0:
        raise ValueError("x must be zero or positive")
    if y > 0:
        raise ValueError("y must be zero or negative")
    z = x*y
    assert z < 0, "expected product of +ve and -ve number to be -ve"
    return 1.0/(z-1)



This example cunningly demonstrates:

(1) Using explicit test-and-raise for ensuring that user-supplied 
arguments are always validated;

(2) Using an assertion to test your program logic;

(3) That the assertion in fact will catch an error in the program logic, 
since if you pass x or y equal to zero, the assertion will fail.


Any time you are tempted to write a comment saying "This can't happen, 
but we check for it just in case", that is a perfect candidate for an 
assertion. Since it can't happen, it doesn't matter if it goes away with 
the -O flag; but since we're imperfect, and we want to cover ourselves 
just in case it does happen, we perform the test when not optimized.

>From my own code, I have a global constant:

UNICODE_NUMERALS = u'\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17
\uff18\uff19'


And then to make sure I haven't missed any:

assert len(UNICODE_NUMERALS) == 10


In another function, I validate a mapping {key:value} to ensure that all 
the values are unique:

seen_values = set()
for k,v in mapping.items():
    if v in seen_values:
        raise ValueError('duplicate value %s' % k)
    seen_values.add(v)
# If we get here without error, then the mapping contains no 
# duplicate values.
assert len(seen_values) == len(mapping)


The assertion acts as a double-check on my logic, not the data. If my 
logic is wrong (perhaps there is a way to get past the for-loop while 
there is a duplicate?) then the assertion will catch it.


-- 
Steven



More information about the Python-list mailing list