[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