Regardless of how this is implemented, you haven't been very clear about the behavior you want.
My objective is to write code (i.e., a combination of validate_string() and main()) that works off test after test and only prints the string if none of the tests in validate_string() fails.
This is simple enough to achive. But what should happen when a test fails? Do you want to know why it didn't print? If not this isn't validation. This is filtering. 80% of the work here isn't when this passes. It's when it doesn't.
One could naively imagine utilizing a custom function that employs exception handling to simply break the script if any of the tests in function validate_string() fails. That way the code exits before the print command in main() is executed. However, to me such a design misuses exception handling and I would prefer a different design.
No, that's not a misue of exception handling. That's not handling exceptions.
Exeception handling is when you catch an exception and recover from it. You close any opened resources and put the system back into a useable state. Likely you'll want to log the exception so people can debug the system. You may also need to notify the user of what happened so they stop waiting for something to happen.
Not handling an exception means the program is going to terminate. That also may be what you want. Terminating when an exception happens ensures the system doesn't go into a weird state and start destroying things you care about.
You need to decide what should happen when the string is invalid.
By calling your method
validate_string() you make it seem like something bad has happened when it fails. If all you want is for the system to queitly not print the "invalid" string and then go about the rest of it's business as if nothing was ever wrong then your string isn't "invalid", it's filtered.
filter_string() would be a better name.
The tests are diverse (and may even be complex functions themselves), and "passing" a test may mean different things depending on the specific test. For example, some tests can be discretized to result in True/False. Other tests are considered "passed" if the underlying function doesn't raise an exception.
The tests may be diverse but we need to make them consistent one way or another to work with them. You can do that by either rewriting the tests or wrapping the tests that need it in something that makes them consistent.
If we make them all return a bool when the execption is something we want to recover from this works:
def printValidString(str, predicate_list):
if all(f(str) for f in predicate_list):
This will only print if all checks pass. Any unhandled exection will blow right out of that and cause us to terminate. Any handled exception can be turned into a false and supress the printing.
def isSunny(mayThrow, args):
Why is this an ok design? Because the prohibition against using exceptions to branch is about if else being better. If else doesn't work when moving through contexts.
Is this an awesome design? No. It would be much better to have consistant checks from the start and use them, well, consistantly. But presuming you're stuck with inconsistant checks, wrapping them to make them consistant works.
Under this design there are three posible outcomes.
- Every check passes, the string is printed, and execution continues
- A check fails in an expected way, the string is not printed, and execution continues
- a check returned false on it's own
isSunny() ran a check, handled an expected exception, and returned false
- A check fails in an unexpected way, the string is not printed, and execution is halted after the unexpected exception bubbles up the call stack
Is something like that what you had in mind?