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"),
# 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):
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 .
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
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
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.