I'm still not sure whether to use exceptions or guard clauses for dealing with invalid arguments.
Guard clauses and exceptions aren't mutually-exclusive. Failing the condition in a guard clause is often grounds for throwing an exception. In a lot of ways, something like
guard_against_none(value) is a shorthand for "throw something if
It seems that Python is more lax with exceptions and in fact encourages them to be used for control flow. E.g., the StopIterationError. Does this extend to using exceptions to handle argument validation?
Don't get laxity and a different paradigm confused.
A number of languages people tend to learn early these days impose a significant penalty for using exceptions. That fosters a general "exceptions considered harmful," mentality that sticks because there's no context for it. Nobody bothers to tell noobs that it's a limitation in the language and not with exceptions in general.
Python is not one of those languages. Exceptions are cheap and efficient enough that using them for control flow is just fine and can make for easier-to-read code.
To me, getting an invalid argument is not "exceptional".
This code would beg to disagree:
def divide(dividend, divisor):
"""Do a division. The divisor shouldn't be zero."""
return dividend / divisor
print divide(12345, 0)
Even if you don't check
divisor, the divide operation in the returned expression is going to raise a
ZeroDivisionError for you. That says that a division by zero happened, but it doesn't give the caller any hint whether it was because of a bug in the function or because the divisor passed into it wasn't valid according to its documentation.
It is actually expected to happen about 10% of the time. ... Should it therefore fail more loudly?
Yes. No. Maybe. That's a decision you have to make based on your circumstances.
This specific case turns the invalid into the valid. The guard clauses as you wrote them don't really guard against anything. They codify an undocumented semantic that says the function will return silently without doing anything if given invalid input. This needs to be part of the function documentation so anyone calling the function understands the potential pitfall.
If your program contains a mix of cases, you need different functions that provide different behaviors:
def add_document(data, country):
"""Add a document."""
raise ValueError("Invalid argument(s).")
# Add the document
def quietly_add_document(data, country):
"""Add a document, ignoring invalid input by doing nothing."""
pass # Don't care if arguments were bogus.
Does Python Zen "Errors should never pass silently." apply here?
I'm not one for "never x"-type dicta; in this case I'd say it does apply in an "errors should never pass silently except where they should" sort of way.
Or should exceptions still be reserved for cases where either they have to be handled, or the software must be made to terminate?
Handling and termination are the only possible outcomes of an exception. Termination is there for cases where you know something wrong and none of the callers all the way up to the main program knows how to deal with it.