For a series of validation checks, each of which has a corresponding error message or other response, I prefer a table-structured approach, rather than nested conditionals.
validations = (
# Validation callable # Error response
(lambda s: '@' in s, "must have '@' in it"),
(lambda s: len(s) > 6, "is too short"),
(lambda s: '.' in s, "must have '.' in it"),
)
def email_invalid(address):
# type: str -> Union[str,None]
"""Check address against a series of rules which must all pass.
Returns an error string on failure; None on success.
"""
for valid, message in validations:
if not valid(address):
return message
Using this in your while
loop is left as an exercise for the reader.
Note the name of the function is email_invalid
, because it returns None
on success and a non-empty string (which bool
evaluates as True
), so it works kind of as a double-negative. I have also chosen to make the validators themselves positive assertions for two reasons: it makes the lambdas more compact, so they fit better into the table and because it's easier to think about positive assertions [citation needed].
The benefits of this approach are that the validators are more prominent than when they're nested down in conditionals and they're easier to add, remove or disable by commenting-out. Since they can be programmatically extracted, tests can be written for the individual validators (which might not be necessary here, but applies generally). Validators that are more complicated than fit into a lambda
can be regular functions (or even classes with a __call__
method).
Depending on the circumstances, it might be preferable to perform all the validations and return a list of error messages, so the user can fix them all at once. With this approach, it's a simple change to use a list comprehension instead of a for
loop (and, like None
, an empty list is also False
).
The drawbacks of this approach are that lambda
and using higher-order functions might be too advanced, depending on your (or your team's) skill or knowledge level; you have to be careful with polarity, both in deciding which to use where and in naming; and even if you know how to do this, it's often not until you've written it with conditionals that you realize you could have structured it like this, so it usually happens by refactoring.
[email protected]
is a valid email address which is rejected by your code. Why? \$\endgroup\$