On Tue, Jan 16, 2018 at 07:37:29AM -0800, smarie wrote:
The problem with a statement called "validate" is that it will break a huge number of programs that already include functions and methods using that name.
You definitely make a point here. But that would be the case for absolutely *any* language evolution as soon as the proposed statements are plain old english words. Should it be a show-stopper ? I dont think so.
It is not a show-stopper, but it is a very, very larger barrier to adding new keywords. If there is a solution to the problem that doesn't require a new keyword, that is almost always preferred over breaking people's code when they upgrade.
But apart from the use of a keyword, we already have a way to do almost exactly what you want:
if not expression: raise ValidationError(message)
after defining some appropriate ValidationError class. And it is only a few key presses longer than the proposed:
validate expression, ValidationError, message
This is precisely what is not good in my opinion: here you do not separate <validation means> from <validation intent>. Of course if <validation means> is just a "x > 0" statement, it works, but now what if you rely on a 3d-party provided validation function (or even yours) such as e.g. "is_foo_compliant" ?
There's no need to invent a third-party validation function. It might be my own validation function, or it might be a simple statement like:
if variable is None: ...
which can fail with NameError if "variable" is not defined. Or "x > 0" can fail if x is not a number.
Regardless of what the validation check does, there are two ways it can not pass:
- the check can fail;
- or the check can raise an exception.
The second generally means that the check code itself is buggy or incomplete, which is why unittest reports these categories separately.
That is a good thing, not a problem to be fixed. For example:
# if x < 0: raise ValueError('x must not be negative') validate x >= 0, ValueError, 'x must not be negative'
Somehow my code passes a string to this as x. Wouldn't you, the developer, want to know that there is a code path that somehow results in x being a string? I know I would. Maybe that will become obvious later on, but it is best to determine errors as close to their source as we can.
With your proposed validate keyword, the interpreter lies to me: it says that the check x >= 0 *fails* rather than raises, which implies that x is a negative number. Now I waste my time trying to debug how x could possibly be a negative number when the symptom is actually very different (x is a string).
Hiding the exception is normally a bad thing, but if I really want to do that, I can write a helper function:
def is_larger_or_equal(x, y): try: return x >= y except: return False
If I find myself writing lots of such helper functions, that's probably a hint that I am hiding too much information. Bare excepts have been called the most diabolic Python anti-pattern:
so hiding exceptions *by default* (as your proposed validate statement would do) is probably not a great idea.
The bottom line is, if my check raises an exception instead of passing or failing, I want to know that it raised. I don't want the error to be hidden as a failed check.
if not is_foo_compliant(x): raise ValidationError(message)
What if this third part method raises an exception instead of returning False in some cases ?
Great! I would hope it did raise an exception if it were passed something that it wasn't expecting and can't deal with.
There may be some cases where I want a validation function to ignore all errors, but if so, I will handle them individually with a wrapper function, which let's me decide how to handle individual errors:
def my_foo_compliant(x): try: return is_foo_compliant(x) except SpamError, EggsError: return True except CheeseError: return False except: raise
But I can count the number of times I've done that in practice on the fingers of one hand.
The goal is really to let developers express their applicative intent =(what should be checked and what is the outcome if anything goes wrong), and give them confidence that the statement will always fail the same way, whatever the failure modes /behaviour of the checkers used in the statement.
I don't agree that this is a useful goal for the Python interpreter to support as a keyword or built-in function.
If you want to create your own library to do this, I wish you good luck, but I would not use it and I honestly think that it is a trap: something that seems to be convenient and useful but actually makes maintaining code harder by hiding unexpected, unhandled cases as if they were expected failures.