Specify an alternative exception for "assert"
assert statement gives the possibility to display the text which goes along with the AssertionError exception. Most of the times though, what would be more appropriate is to raise a different exception (e.g. ValueError). My proposal is to be able to specify an exception as a replacement for AssertionError as in:
assert callable(fun), ValueError("object is not a callable") ValueError: object is not a callable
Specifically, this would be useful at the top of a function or method, where argument types or values are usually checked: def retry(times=3, timeout=0.1, callback=None): assert times >= 1, ValueError("times must be >= 1") assert isinstance(timeout, (int, float)), ValueError("invalid timeout") assert callable(callback), ValueError("callback is not a callable") ...as opposed to: def retry(times=3, timeout=0.1, callback=None): if not times >= 1: raise ValueError("times must be >= 1") if not isinstance(timeout, (int, float)): raise ValueError("invalid timeout") if not callable(callback): raise ValueError("callback is not a callable") Other than saving 1 line for each type/value check, this has the advantage that the assertion logic (e.g. "times >= 1") is shown in the traceback message itself, because it's on the same line, enriching the context and giving more information in case of error. Thoughts? -- Giampaolo - http://grodola.blogspot.com
Other than the fact that this would completely fail when run with -O... I believe I brought this up a while back (~1-2 years), and that was the reply. -- Ryan [ERROR]: Your autotools build scripts are 200 lines longer than your program. Something’s wrong. http://kirbyfan64.github.io/ On May 2, 2016 9:23 AM, "Giampaolo Rodola'" <g.rodola@gmail.com> wrote:
assert statement gives the possibility to display the text which goes along with the AssertionError exception. Most of the times though, what would be more appropriate is to raise a different exception (e.g. ValueError). My proposal is to be able to specify an exception as a replacement for AssertionError as in:
assert callable(fun), ValueError("object is not a callable") ValueError: object is not a callable
Specifically, this would be useful at the top of a function or method, where argument types or values are usually checked:
def retry(times=3, timeout=0.1, callback=None): assert times >= 1, ValueError("times must be >= 1") assert isinstance(timeout, (int, float)), ValueError("invalid timeout") assert callable(callback), ValueError("callback is not a callable")
...as opposed to:
def retry(times=3, timeout=0.1, callback=None): if not times >= 1: raise ValueError("times must be >= 1") if not isinstance(timeout, (int, float)): raise ValueError("invalid timeout") if not callable(callback): raise ValueError("callback is not a callable")
Other than saving 1 line for each type/value check, this has the advantage that the assertion logic (e.g. "times >= 1") is shown in the traceback message itself, because it's on the same line, enriching the context and giving more information in case of error.
Thoughts?
-- Giampaolo - http://grodola.blogspot.com
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Mon, May 2, 2016 at 8:01 AM, Ryan Gonzalez <rymg19@gmail.com> wrote:
Other than the fact that this would completely fail when run with -O...
But maybe that's fine, or intended. I can see a fair number of uses for this, including subclasses of AssertionError. +0.5 -- --Guido van Rossum (python.org/~guido)
On Mon, May 2, 2016, at 11:14, Guido van Rossum wrote:
On Mon, May 2, 2016 at 8:01 AM, Ryan Gonzalez <rymg19@gmail.com> wrote:
Other than the fact that this would completely fail when run with -O...
But maybe that's fine, or intended.
I can see a fair number of uses for this, including subclasses of AssertionError.
I think this would be an attractive nuisance, and if it's implemented at all it should forbid types that are *not* subclasses of AssertionError in order to mitigate that.
On Mon, May 2, 2016 at 5:17 PM, Random832 <random832@fastmail.com> wrote:
On Mon, May 2, 2016, at 11:14, Guido van Rossum wrote:
On Mon, May 2, 2016 at 8:01 AM, Ryan Gonzalez <rymg19@gmail.com> wrote:
Other than the fact that this would completely fail when run with -O...
But maybe that's fine, or intended.
I can see a fair number of uses for this, including subclasses of AssertionError.
I think this would be an attractive nuisance, and if it's implemented at all it should forbid types that are *not* subclasses of AssertionError in order to mitigate that.
Why such a constraint? I think most of the times the desired exception would be either ValueError or TypeError, both of which do not inherit from AssertionError. -- Giampaolo - http://grodola.blogspot.com
On Mon, May 2, 2016, at 11:28, Giampaolo Rodola' wrote:
Why such a constraint? I think most of the times the desired exception would be either ValueError or TypeError, both of which do not inherit from AssertionError.
The point is that it's not appropriate to use assert for checks that are part of the semantics of the function. If all that you gain is having the check logic on the same source line as the raise for the stack trace, why not simply do "if value < 1: raise ValueError('value must be >= 1')"? if/raise is only two more characters than assert. "if not/raise" is a *bit* more, but not enough to worry about. You could trim off a character (and have a better style if we're squeamish about single-line suites) by adding a "raise ... if ..." syntax.
I feel like you just brought up the exact issue. Assertions should *never* be used for things like this. Because, one day, some stupid idiot is going to use assertions to perform some sort of data validation, someone else will use that library with -O, and the world will explode. It's just far too attractive to use but far too easy to misuse. And, if you really want: if my_cond: raise MyError('abc') Compare that to: assert my_cond, MyError('abc') Also, RPython uses assertions for error handling. Trust me, dealing with that is *not* fun. -- Ryan [ERROR]: Your autotools build scripts are 200 lines longer than your program. Something’s wrong. http://kirbyfan64.github.io/ On May 2, 2016 10:29 AM, "Giampaolo Rodola'" <g.rodola@gmail.com> wrote:
On Mon, May 2, 2016 at 5:17 PM, Random832 <random832@fastmail.com> wrote:
On Mon, May 2, 2016, at 11:14, Guido van Rossum wrote:
On Mon, May 2, 2016 at 8:01 AM, Ryan Gonzalez <rymg19@gmail.com> wrote:
Other than the fact that this would completely fail when run with -O...
But maybe that's fine, or intended.
I can see a fair number of uses for this, including subclasses of AssertionError.
I think this would be an attractive nuisance, and if it's implemented at all it should forbid types that are *not* subclasses of AssertionError in order to mitigate that.
Why such a constraint? I think most of the times the desired exception would be either ValueError or TypeError, both of which do not inherit from AssertionError.
-- Giampaolo - http://grodola.blogspot.com
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
I feel like you just brought up the exact issue. Assertions should *never* be used for things like this. Because, one day, some stupid idiot is going to use assertions to perform some sort of data validation, someone else will use that library with -O, and the world will explode.
That's a very strong opinion, and a bit of a doomsday scenario. I hear worries about this a lot, but I've never heard a story from someone to whom
On Mon, May 2, 2016 at 8:38 AM, Ryan Gonzalez <rymg19@gmail.com> wrote: this actually happened.
It's just far too attractive to use but far too easy to misuse.
The thing is, assert exists and is not going away. People are writing these asserts today (there are still many in asyncio). In many cases the assert is not guarding against bad data or user input, but simply against a confused caller. The assumption is that once the program is debugged enough to go to production (where you use -O) the need for the asserts has gone down enough that it doesn't matter the checks are gone -- the confused call site has long been found during tests and fixed.
And, if you really want:
if my_cond: raise MyError('abc')
Compare that to:
assert my_cond, MyError('abc')
There's a bug in your example, and that bug is a good argument for using assert: the equivalent of that assert should be if not my_cond: raise MyError('abc') and that extra inversion often makes the code a little less straightforward. Compare if times < 1: raise ValueError() to assert times >= 1: raise ValueError() The latter states positively what we're expecting. This is much more helpful for the reader. Also, RPython uses assertions for error handling. Trust me, dealing with
that is *not* fun.
RPython is not Python; isn't that a feature? :-) -- --Guido van Rossum (python.org/~guido)
On May 2, 2016 10:55 AM, "Guido van Rossum" <guido@python.org> wrote:
On Mon, May 2, 2016 at 8:38 AM, Ryan Gonzalez <rymg19@gmail.com> wrote:
I feel like you just brought up the exact issue. Assertions should
*never* be used for things like this. Because, one day, some stupid idiot is going to use assertions to perform some sort of data validation, someone else will use that library with -O, and the world will explode.
That's a very strong opinion, and a bit of a doomsday scenario. I hear
It's just far too attractive to use but far too easy to misuse.
The thing is, assert exists and is not going away. People are writing
worries about this a lot, but I've never heard a story from someone to whom this actually happened. these asserts today (there are still many in asyncio). In many cases the assert is not guarding against bad data or user input, but simply against a confused caller. The assumption is that once the program is debugged enough to go to production (where you use -O) the need for the asserts has gone down enough that it doesn't matter the checks are gone -- the confused call site has long been found during tests and fixed. I would still prefer the proposal that requires the errors to derive from AssertionError, since there *are* people that will use this for some sort of data validation. It's more likely because of Python's "easier to ask forgiveness than permission" idea; imagine someone "asserts" with a custom exception that some sort of input data is correct/safe. Then someone else uses the library in their website. With -O. If it's restricted to subclasses of AssertionError, then people will be less likely to use it for random error checking that *should* run in release mode.
And, if you really want:
if my_cond: raise MyError('abc')
Compare that to:
assert my_cond, MyError('abc')
There's a bug in your example, and that bug is a good argument for using assert: the equivalent of that assert should be
if not my_cond: raise MyError('abc')
and that extra inversion often makes the code a little less straightforward. Compare
if times < 1: raise ValueError()
to
assert times >= 1: raise ValueError()
The latter states positively what we're expecting. This is much more helpful for the reader.
Also, RPython uses assertions for error handling. Trust me, dealing with
Well, I was also typing from a phone while walking through the mall, so that may have something to do with the mistake. :) that is *not* fun.
RPython is not Python; isn't that a feature? :-)
It is! :)
-- --Guido van Rossum (python.org/~guido)
-- Ryan [ERROR]: Your autotools build scripts are 200 lines longer than your program. Something’s wrong. http://kirbyfan64.github.io/
On Mon, May 2, 2016 at 9:32 AM, Ryan Gonzalez <rymg19@gmail.com> wrote:
I would still prefer the proposal that requires the errors to derive from AssertionError, since there *are* people that will use this for some sort of data validation. It's more likely because of Python's "easier to ask forgiveness than permission" idea; imagine someone "asserts" with a custom exception that some sort of input data is correct/safe. Then someone else uses the library in their website. With -O. If it's restricted to subclasses of AssertionError, then people will be less likely to use it for random error checking that *should* run in release mode.
I don't believe your logic is correct. And the OP most assuredly does *not* want to raise a subclass of AssertionError. He's written enough Python code to know what he's talking about, so I take him serious -- whereas your argument so far is mere FUD. -- --Guido van Rossum (python.org/~guido)
On Mon, May 2, 2016 at 6:32 PM, Ryan Gonzalez <rymg19@gmail.com> wrote:
On May 2, 2016 10:55 AM, "Guido van Rossum" <guido@python.org> wrote:
On Mon, May 2, 2016 at 8:38 AM, Ryan Gonzalez <rymg19@gmail.com> wrote:
I feel like you just brought up the exact issue. Assertions should
*never* be used for things like this. Because, one day, some stupid idiot is going to use assertions to perform some sort of data validation, someone else will use that library with -O, and the world will explode.
That's a very strong opinion, and a bit of a doomsday scenario. I hear
It's just far too attractive to use but far too easy to misuse.
The thing is, assert exists and is not going away. People are writing
worries about this a lot, but I've never heard a story from someone to whom this actually happened. these asserts today (there are still many in asyncio). In many cases the assert is not guarding against bad data or user input, but simply against a confused caller. The assumption is that once the program is debugged enough to go to production (where you use -O) the need for the asserts has gone down enough that it doesn't matter the checks are gone -- the confused call site has long been found during tests and fixed.
I would still prefer the proposal that requires the errors to derive from AssertionError
The proposal I'm making is the exact opposite (allow a different exception than AssertionError) so I'm not sure I understand your objection.
, since there *are* people that will use this for some sort of data validation.
Personally I never used asserts for data validation. Usually they are used: #1 in unit tests, if you don't like using the verbose unittest's self.assert* APIs #2 in application code as an extra safety to make sure, e.g., that the function's caller is passing the "right things", that an attribute has a certain value at some point, etc. Note that #2 is meant to help the developer(s), not the final user. It's something which is there as a guard against possible mistakes but not much more, and to my understanding this is how most people feel about "assert"s. Could allowing to raise a specific and "more correct" exception type as a one-liner be an incentive to use "assert" to validate user input? I don't know, but my impression is that: #1 most people are "educated" enough to know that assert is usually not meant to validate user input #2 very few people use -O (e.g. I believe I never used it myself) #3 I expect whoever uses -O is supposed to be aware of what are the implications of it -- Giampaolo - http://grodola.blogspot.com
On Mon, May 2, 2016 at 8:17 AM, Random832 <random832@fastmail.com> wrote:
On Mon, May 2, 2016, at 11:14, Guido van Rossum wrote:
On Mon, May 2, 2016 at 8:01 AM, Ryan Gonzalez <rymg19@gmail.com> wrote:
Other than the fact that this would completely fail when run with -O...
But maybe that's fine, or intended.
I can see a fair number of uses for this, including subclasses of AssertionError.
I think this would be an attractive nuisance, and if it's implemented at all it should forbid types that are *not* subclasses of AssertionError in order to mitigate that.
You can't make allegations of being an attractive nuisance without explaining your reasoning. Why do you think this increases bad behavior? What kind of bad behavior are you thinking of? -- --Guido van Rossum (python.org/~guido)
On Mon, May 2, 2016, at 11:33, Guido van Rossum wrote:
You can't make allegations of being an attractive nuisance without explaining your reasoning. Why do you think this increases bad behavior? What kind of bad behavior are you thinking of?
Using assert for checks that are intended to be permanent rather than for debug purposes (i.e. the *reason* it would fail when run with -O); I thought that was obvious.
On May 2, 2016 11:18 AM, "Random832" <random832@fastmail.com> wrote:
On Mon, May 2, 2016, at 11:14, Guido van Rossum wrote:
On Mon, May 2, 2016 at 8:01 AM, Ryan Gonzalez <rymg19@gmail.com> wrote:
Other than the fact that this would completely fail when run with
-O...
But maybe that's fine, or intended.
I can see a fair number of uses for this, including subclasses of AssertionError.
I think this would be an attractive nuisance, and if it's implemented at all it should forbid types that are *not* subclasses of AssertionError in order to mitigate that.
What about wrapping the error in an AssertionError? The error being raised is really an assertion error, since it came from an assert. A wrapped ValueError isn't going to be caught by an `except ValueError:` clause. That might be a good thing: failed asserts usually shouldn't be caught, since they indicate a bug. Even if they are caught (e.g. for logging), they shouldn't be caught by something expecting a non-AssertionError.
On Mon, May 02, 2016 at 04:23:14PM +0200, Giampaolo Rodola' wrote:
assert statement gives the possibility to display the text which goes along with the AssertionError exception. Most of the times though, what would be more appropriate is to raise a different exception (e.g. ValueError). My proposal is to be able to specify an exception as a replacement for AssertionError as in:
assert callable(fun), ValueError("object is not a callable") ValueError: object is not a callable
My apologies for the length of this post. For the benefit of those in a hurry, here is the executive summary, or TL;DR version: Strong -1 for this proposal. Assertions have different semantics to explicit "if condition: raise" checks, and it is good and useful that they behave differently. In particular, a failed assert should always be a bug, and never an expected exception which the user may wish to catch. We should not blur the difference between an assert and an explict if...raise just for the sake of saving a line of code. If you find yourself wanting assert to raise a different exception type (like TypeError), that is a warning sign that you shouldn't be using assert: you're probably abusing assert for code code that needs the if...raise semantics, not the assert semantics. Longer, and hopefully more articulate explanation (and hopefully not too rambling) follows: I work with some people who insist on always using "assert" for all their error checking. ALL of it, whether of public or private functions, including end-user data validation. Because "it saves a line" and "it makes it easy to remember what exception to catch".[1] So their code is riddled with "try...except AssertionError", and heaven help us if the end user runs their code with -O. "It doesn't matter, nobody will do that." (Fortunately, so far the end users have not been savvy enough to know about -O.) Assertions have a number of uses, such as checked comments, testing invariants, contract checking[2], etc. Some time ago, I wrote this to try to explain what I believe are good and bad uses of assert: http://import-that.dreamwidth.org/676.html I believe that assert (as opposed to an explicit test and raise) strongly communicates the intent of the programmer: assert condition, "error" says that this is an internal check which (in a bug-free program) is safe to disable. The assertion can be thought of as a form of "trust, but verify". There are a few different interpretations of assert, but they all agree that *assertions are safe to remove* (provided the program is bug-free). Once you are confident that the assertions will never trigger, you can safely disable them, and your program should still work. Whereas an explicit test and raise: if not condition: raise Exception("error") strongly says that this is checking *external* input from a source that cannot be trusted (say, user supplied input, or external code that doesn't obey your code's internal contracts). Rather than "trust but verify" it is more "don't trust", and even in a bug-free program, you cannot do without this check. You can never disable these checks. (By trust, I don't necessarily mean that the user or code is actively hostile and trying to subvert your function. I just mean that you cannot trust that it will provide valid input to your function. You MUST perform the check, even in a bug-free program.) I think that the difference between those two sorts of checks is important, and I do not wish to see the distinction weakened or removed. I think that this proposal will weaken that distinction by allowing assert to raise non-AssertionError and encouraging people to treat it as a lazy shortcut for if...raise. I think it will make it harder to distinguish between program bugs and expected errors that can be caught. More on this below. (At the risk of weakening my own argument, I acknowledge that there are grey areas where there may be legitimate difference of opinion whether a particular check is better as an assert or an if...raise. Sometimes it comes down to the programmer's opinion. But I think it is still important to keep the distinction, and not blur the two cases just for the sake of those grey areas, and especially not just to save a line and a few keystrokes.) AssertionError strongly indicates an internal program bug (a logic error, a failed checked comment, a contract violation etc). Consequently, I should never need to catch AssertionError directly. AssertionError is always a failure of "trust, but verify" and therefore an internal bug. And importantly, no other exceptions should count as this sort of failure. (I'm giving an ideal, of course, and in real life people vary in how rigourously they apply the ideals I describe. Some people seemingly choose exceptions arbitrarily. No doubt we all have to deal with badly chosen exceptions. But just because people will misuse exceptions no matter what we do, doesn't mean we should add syntax to make it easier to misuse them.) For example, contract violations shouldn't raise ValueError, because then the caller might treat that contract violation (a bug) as an expected error and catch the exception. Same goes for invariants and checked comments: process(thelist) # if we get here, the list has at least two items assert len(thelist) >= 2, 'error' The intent here is that AssertionError is *not* part of the function API, it should not be treated as an expected error which the caller can catch and deal with. AssertionError signals strongly "don't think about catching this, it's a bug that must be fixed, not an expected error". If the failure *is* expected, then I ought to communicate that clearly by using an explicit if...raise with a different exception: process(thelist) # if we get here and the list has less than two items, that's an error if len(thelist) < 2, ValueError('error') But with the proposed change, the code can send mixed messages: assert len(thelist) >= 2, ValueError('error') The assert says that this can be safely discarded once the program is bug-free, i.e. that the exception should never be raised. But the ValueError says that the exception is expected and the test shouldn't be removed. If ValueError does get raised, is that a failed invariant, i.e. a bug? Or an expected error that the caller can and should deal with? Who can tell? Is it safe to disable that assertion? The intention of the programmer is harder to tell, and there are more ways to get it wrong. [1] I admit it: sometimes I'm lazy and use assert this way too. We're all human. But never in production code. [2] As in Design By Contract. -- Steve
My sentiments almost exactly. The only 'however' I have is with respect to the 'Design By Contract' part, as I do not think of `assert` as suitable for checking the pre-condition. The way I use `assert` is that I only use it for the cases where the `assert` can not be triggered except for when other programmers circumvent the API. I use it for: * Checking loop invariants (when especially tricky). * Checking class invariants. * Checking function arguments to protected/private functions and methods. But do not use it for: * Checking function arguments for public methods. * Checking public attributes of a class. I am not sure if by 'Design by Contract' you also mean the function arguments of public functions. Ideally, when I have written a class, none of the assertions get triggered unless *I* have made a bug in the implementation of said class. In particular, bugs (or mistakes in reasoning) in other classes should not be able to trigger the assertions. To me, `AssertionError` means: The implementor of said functionality made a mistake. It should never mean: "The developer called the library incorrectly", but always: "The library is broken.". However, even though I'm also -1 on the proposal as it stands, it also makes me recognise there is something missing. To be precise: a clearly defined way of doing expensive argument checking only when no '-O' is supplied. To me, `assert foo, bar` is (really desireable sugar) for if __debug__ and not (foo): raise AssertionError(bar) And I could likewise envision wanting sugar for if __debug__ and not (foo): raise bar Where `foo` is a really expensive check (or in a tight loop!). In cases where the source is external to the library/class/function, an `AssertionError` is not the right case. def bisect(values, value): # does not make sense, as `values` comes from outside. assert is_sorted(values) # does make sense, but is 'more expensive'. if __debug__ and not is_sorted(values): raise ValueError("Expected values to be sorted") # business-logic! Now it is tempting to re-use `assert` for that, as it also has the __debug__ sugarring. But using `assert` is not correct, as `assert` is not supposed to be used for checking across boundaries. On the other hand, it might also be a cue to look into solutions for supporting pre/post-condition checking in an elegant manner. As I am writing this, it does however deem to me that it is a matter of purism vs pragmatism. What is desired is some 'checking' that is to be done only during development. So from that point, I see 3 ways forward: 1. Add new syntax for the `raise bar` case above. However, I can not really see a good name for that. Maybe `check` or `verify`, but changes are quite high that would introduce a new keyword. 2. Add support for not only strings, but also exception-subclasses as the second argument to an `assert`. 3. Do nothing. Really tempting. The first approach is most pure, but will in essence boil down to the third approach because it is most expensive. The second approach seems like an impure, pragmatic and viable approach. I may not like it, but I think it stands more of a chance than the more pure (1), and delivers more value than (3). Kind regards, Sjoerd Job On Tue, May 03, 2016 at 04:07:13AM +1000, Steven D'Aprano wrote:
On Mon, May 02, 2016 at 04:23:14PM +0200, Giampaolo Rodola' wrote:
assert statement gives the possibility to display the text which goes along with the AssertionError exception. Most of the times though, what would be more appropriate is to raise a different exception (e.g. ValueError). My proposal is to be able to specify an exception as a replacement for AssertionError as in:
assert callable(fun), ValueError("object is not a callable") ValueError: object is not a callable
My apologies for the length of this post. For the benefit of those in a hurry, here is the executive summary, or TL;DR version:
Strong -1 for this proposal.
Assertions have different semantics to explicit "if condition: raise" checks, and it is good and useful that they behave differently. In particular, a failed assert should always be a bug, and never an expected exception which the user may wish to catch. We should not blur the difference between an assert and an explict if...raise just for the sake of saving a line of code. If you find yourself wanting assert to raise a different exception type (like TypeError), that is a warning sign that you shouldn't be using assert: you're probably abusing assert for code code that needs the if...raise semantics, not the assert semantics.
Longer, and hopefully more articulate explanation (and hopefully not too rambling) follows:
I work with some people who insist on always using "assert" for all their error checking. ALL of it, whether of public or private functions, including end-user data validation. Because "it saves a line" and "it makes it easy to remember what exception to catch".[1] So their code is riddled with "try...except AssertionError", and heaven help us if the end user runs their code with -O. "It doesn't matter, nobody will do that." (Fortunately, so far the end users have not been savvy enough to know about -O.)
Assertions have a number of uses, such as checked comments, testing invariants, contract checking[2], etc. Some time ago, I wrote this to try to explain what I believe are good and bad uses of assert:
http://import-that.dreamwidth.org/676.html
I believe that assert (as opposed to an explicit test and raise) strongly communicates the intent of the programmer:
assert condition, "error"
says that this is an internal check which (in a bug-free program) is safe to disable. The assertion can be thought of as a form of "trust, but verify". There are a few different interpretations of assert, but they all agree that *assertions are safe to remove* (provided the program is bug-free). Once you are confident that the assertions will never trigger, you can safely disable them, and your program should still work.
Whereas an explicit test and raise:
if not condition: raise Exception("error")
strongly says that this is checking *external* input from a source that cannot be trusted (say, user supplied input, or external code that doesn't obey your code's internal contracts). Rather than "trust but verify" it is more "don't trust", and even in a bug-free program, you cannot do without this check. You can never disable these checks.
(By trust, I don't necessarily mean that the user or code is actively hostile and trying to subvert your function. I just mean that you cannot trust that it will provide valid input to your function. You MUST perform the check, even in a bug-free program.)
I think that the difference between those two sorts of checks is important, and I do not wish to see the distinction weakened or removed. I think that this proposal will weaken that distinction by allowing assert to raise non-AssertionError and encouraging people to treat it as a lazy shortcut for if...raise. I think it will make it harder to distinguish between program bugs and expected errors that can be caught. More on this below.
(At the risk of weakening my own argument, I acknowledge that there are grey areas where there may be legitimate difference of opinion whether a particular check is better as an assert or an if...raise. Sometimes it comes down to the programmer's opinion. But I think it is still important to keep the distinction, and not blur the two cases just for the sake of those grey areas, and especially not just to save a line and a few keystrokes.)
AssertionError strongly indicates an internal program bug (a logic error, a failed checked comment, a contract violation etc). Consequently, I should never need to catch AssertionError directly. AssertionError is always a failure of "trust, but verify" and therefore an internal bug. And importantly, no other exceptions should count as this sort of failure.
(I'm giving an ideal, of course, and in real life people vary in how rigourously they apply the ideals I describe. Some people seemingly choose exceptions arbitrarily. No doubt we all have to deal with badly chosen exceptions. But just because people will misuse exceptions no matter what we do, doesn't mean we should add syntax to make it easier to misuse them.)
For example, contract violations shouldn't raise ValueError, because then the caller might treat that contract violation (a bug) as an expected error and catch the exception. Same goes for invariants and checked comments:
process(thelist) # if we get here, the list has at least two items assert len(thelist) >= 2, 'error'
The intent here is that AssertionError is *not* part of the function API, it should not be treated as an expected error which the caller can catch and deal with. AssertionError signals strongly "don't think about catching this, it's a bug that must be fixed, not an expected error".
If the failure *is* expected, then I ought to communicate that clearly by using an explicit if...raise with a different exception:
process(thelist) # if we get here and the list has less than two items, that's an error if len(thelist) < 2, ValueError('error')
But with the proposed change, the code can send mixed messages:
assert len(thelist) >= 2, ValueError('error')
The assert says that this can be safely discarded once the program is bug-free, i.e. that the exception should never be raised. But the ValueError says that the exception is expected and the test shouldn't be removed. If ValueError does get raised, is that a failed invariant, i.e. a bug? Or an expected error that the caller can and should deal with? Who can tell? Is it safe to disable that assertion? The intention of the programmer is harder to tell, and there are more ways to get it wrong.
[1] I admit it: sometimes I'm lazy and use assert this way too. We're all human. But never in production code.
[2] As in Design By Contract.
-- Steve _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
participants (7)
-
Franklin? Lee
-
Giampaolo Rodola'
-
Guido van Rossum
-
Random832
-
Ryan Gonzalez
-
Sjoerd Job Postmus
-
Steven D'Aprano