[Python-ideas] Raise exception if (not) true

spir denis.spir at gmail.com
Fri Feb 21 16:28:51 CET 2014


On 02/21/2014 07:11 AM, Andrew Barnert wrote:
> On Feb 20, 2014, at 18:23, spir <denis.spir at gmail.com> wrote:
>
>> On 02/20/2014 10:28 PM, Steven D'Aprano wrote:
>>> On Thu, Feb 20, 2014 at 09:44:57PM +0100, spir wrote:
>>>
>>>>> But I would like to be able to add an error type to assertions (in addition
>>>>> to the optional message). This is particularly useful for people (like me)
>>>>> who systematically check func inputs (for client debugging comfort), using
>>>>> assert's.
>>> Then your code is systematically broken, and badly so.
>>
>> Why do you speak _that_ negatively? (or should I say: _that_ violently?) And this, maybe not _that_ logically? Do you own the (copy)rights on proper usage of assert's?
>>
>>> All anyone needs
>>> to do to disable your checking is pass -O to the Python interpreter.
>>>
>>> assert is not a short-cut for lazy programmers to avoid having to write
>>> an explicit "if cond: raise SomethingAppropriate(message)". Assertions
>>> have specific uses. You should read this post I made last November:
>>>
>>> https://mail.python.org/pipermail/python-list/2013-November/660401.html
>>
>> I think you are wrong, at least in this very case. Assert's for me are a debugging tool (or maybe more generally a tool for improving reliability). Such checks help finding errors and correcting them (thank to hopefully clear error messages), with a higher chance these corrections happen before "too late", meaning before users pay for our bugs. What else is the proper usage of assertions?
>>
>> If not checked on function input, either with assert's or an if-raise combination, execution will break anyway, just later (later in time, and slightly further in code), because some input variable's value or type is wrong. Assert's placed that way are not strictly necessary (it's like duck typing: you don't need to check), instead they're a helpful tool for all users of your "service".
>>
>>     def average (numbers):
>>         n = len(numbers)
>>         assert n != 0, "Cannot compute average of 'zero number'.", ValueError
>>         return sum(numbers) / n
>
> I think you're actually suffering from the confusion you accused Steven of. His post explains the difference between internal preconditions and external value checks. Then difference has nothing to do with the form of the function, but with how it's used.
>
> If average is only called by your own code with your own values, so you know it can never be called with an empty list unless there's a bug somewhere, then you're asserting a precondition, which is exactly what asserts are for--but in that case this should be an AssertionError, not a ValueError.
>
> If average is part of an external API, or is called with user data, so you're testing for something that could fail because of user error rather than a bug in your code, then this is a perfect case for a ValueError--but it's not an assertion, it's an error check.
>
> The fact that assertions happen to work by exception handling doesn't mean you should ignore the difference between them.

See below.

>> [Side-note: I do not use here assertions for exception handling (properly speaking) at all; instead for catching errors (properly speaking). It's just that, in python and a long list of other languages, there is a complete confusion between exceptions (which belong to the app's logic, but need to be processed specially) and errors (which don't, and are our fault and our problem). Is this topic clear?]

I think this is the _relevant_ difference.

You and Steven point at the difference between illogical facts proper to my 
code, say internal, and ones due to client (user) code, say external. This is a 
true difference, indeed, but maybe not that relevant here. If this is an 
external interface, meaning in my example the function 'average' is to be used 
by clients, then calling it with an empty array of numbers is an error in any 
case. Or rather, it is an obvious _anomaly_ on the side of the procedure, which 
i can detect, and it is certainly a *symptom* of a true error on their side. 
(The detected anomaly is but an effect of an error, which is the cause, 
_somewhere else_ in their code.) The reason is obvious: average of 
no-number-at-all makes no sense. [And this precisely is what the error message 
should say, and the improvement I wish to make, instead of a potential 
division-by-zero error which is just a consequence.]

Whether this error is in their code or mine does not change it signicantly, I 
guess. It is an error. [And is is a value error in fact, reason why I wish to 
change the feature to have a better message.] Note that an error is a _break_ of 
the the application logic; it is actual semantics of the program that does not 
belong to the app's logic.

In any case, it is certainly _not_ an exception. That it were an exception would 
mean that this case (the array of numbers is empty) _belongs to_ the application 
logic. If so, the client code should just deal with this special case specially. 
Right? And certainly not call average (blindly). In fact, there are good chances 
that this exceptional case is not only not properly dealt with _here_, where we 
are about to call 'average', but also elsewhere in code; and probably we should 
not reach this very point in code at all, but instead have branched on another 
code path earlier.

Exception handling, exception catching precisely, should (in my view) only be 
used for such exceptional situations that (1) nevertheless belong to the 
application logic, so should be dealt with properly (2) are impredictable on the 
client side, unlike the example case.

Such cases happen for instance with search/find procedures (we don't know 
whether the searched item is there before trying to find it) or in relation with 
elements external to the app's "world", such as the file system or the user. 
This is the proper usage of exception machinaries in my view (and I only use 
them for that). [1]

d

[1] Side-note: I have come to think that the common exception handling 
mechanisms (again, to be used only in impredictable exception cases) are wrong. 
The reason is that only clients know whether a case of anomaly (which would 
cause the procedure to "throw", to "raise") is actually an unknown error (the 
collection should hold this item) or instead an exception (the item may not be 
there). Thus, clients should be in control of exception handling, not service 
procedures. Presently, the latter just lauch the exception machinary (big, 
complicated, with longjumps and stack unwinding mechanisms). Instead, what we 
need is allow clients to tell that this is an exceptional, but impredictable, 
case, and no throwing and catching should happen at all.
Some coding practices end up with a similar result by adding an "optional" param 
to such procedures; in which case if an anomaly happens they just say it, 
somehow, instead of failing.

     def item_index (col, item, opt=false):
         ... search item ...
         if index: return index

         if opt: return None   # or -1
         raise ...

This means, for me, that in a sufficiently flexible language (read: dynamic or 
sophisticated), a programming community could live without exception handling 
features, instead just promote a coding style for such particular service 
procedures (a limited list). One possible view is that languages where this is 
impracticle (too rigid) are otherwise wrongly-designed, but things are not that 
simple, maybe.
Or, we could have a builtin feature making such handling clear & simple. In case 
of predictable exceptions, the simple (and imo right) way is just to use an 
if/else branching.
     if numbers:
         avg = average(numbers)
     else:
         # deal with exception
Similarly, an alternative construct may simply allow not failing in case of 
unpredictable exceptions:
     maybe:
         idx = item_index(col, item)
     else:
         # deal with exception

This superficially looks like typical try/except or try/catch, but in fact here 
no throwing & catching happen at all. The failure is just signaled to the caller 
side, somehow (a plain flag).
We may refine the construct with discrimination of error types, just like 
except/catch branches (may be good, indeed, but requires proper usage by client, 
which is not always well done). But in any case clients control the exception 
handling mechanism, everything is far simpler and easier, and there are no 
unneeded processes behing the stage.


More information about the Python-ideas mailing list