I propose adding
ValueNotFoundError as a subclass of
ValueError so that not found cases can be differentiated from other sources of value errors. This is the same general idea as when we added
ModuleNotFoundError as a subclass of
FileNotFoundError as a subclass of
The new exception addresses a problem facing users of
operator.indexOf(). Currently, there is no clean way to determine whether a
ValueError was caused by missing value or whether there was an unrelated exception in the input sequence or iterable.
For example, the classic approach to locating multiple indices is subtly wrong because it treats
ValueError as always meaning “missing value”. However, if there is a
ValueError in the underlying
Sequence.__getitem__ logic, that exception gets swallowed.
def indices(seq, value, start=0):
"""Return indices where a value occurs in an sequence.
>>> indices('AABCADEAF', 'A')
[0, 1, 4, 7]
result = 
i = start - 1
i = seq.index(value, i+1)
@pochmann has created an artificial example that breaks the above code. Another user ran into a presumably real world problem with
AFAICT the only ways to fix this are 1) egregious stack frame hacks to determine where the exception was raised, 2) fragile hacks that depend on the exception repr, 3) deeming
Sequence.index() to be unusable for user defined types and having to manually loop and compare instead, or 4) adding
ValueNotFoundError so that we can reliably, performantly, and elegantly differentiate the cases.
I propose option 4 as the only way to make sure that everyday uses of
operator.indexOf() aren’t subtly wrong for input types that can raise a
The problem is… how to determine whether a
ValueNotFoundError was caused by missing value or whether there was an unrelated exception in the input sequence or iterable.
We encountered a similar issue with
StopIteration. It was fixed by replacing a
StopIteration raised inside a generator function with a
RuntimeError. We can do the same with
ValueError (or new
ValueNotFoundError). Actually, introducing
ValueNotFoundError without replacing it with
RuntimeError if it was raised inside a generator function will not fix the issue.
On other hand, this issue looks specific for
operator.indexOf(). Could they simply catch
ValueError raised in the iterator and replace it with
More generally, if some function uses specific exception to signal some result, it should catch that exception raised by the input and replace it with other exception.
The difference is that StopIteration can easily, commonly, and naturally be raised from any level in a chain of iterators. In contrast, a ValueError for a missing value is a very specific case separate from all other kinds of ValueError,
math.sqrt(-1), for example.
The other difference is that a
RuntimeError is appropriate for the iterator examples because it specifically disallows a formerly acceptable programming practice. We can’t really do that for
index() logic because value errors can naturally arise inside normal Python code. It is a rather fundamental and generic kind of exception, indicative of an unusable data input rather than a particular programming practice that can be banned.
Also, RuntimeError is usually reserved for exceptions that amount to “the code cannot continue is a recoverable way, so we’re giving you this unlikely to be caught exception instead of crashing.” In contrast, having a try/except for a ValueError is reasonably common because there may be a reasonable way forward.
All that is being proposed is to make a “not found” case specific enough to distinguished from “values that don’t make sense”. IIRC, this was the exact reasoning behind
FileNotFoundError being added as a refinement to
OSError; otherwise, we needed to inspect return codes or error messages to make the distinction for recoverable problems — trying an alternate filename — versus unrecoverable problems like permission errors or network errors.
I agree with you.
ValueError is too general type. It is used in many broad categories of errors and exceptional cases. In some cases it is even used where
TypeError could be more appropriate.
But don’t you think that
ValueNotFoundError raised by the input should be replaced with
RuntimeError to avoid ambiguity?
I don’t think RuntimeError is the right choice, but otherwise, it does seem reasonable to me. In a function whose protocol is to raise an exception, it makes sense to prevent that exception from being raised unintentionally.
Catching a generic ValueError and reraising some other exception would create a problem worse than we have now. How could a user reasonably be expected to catch the new exception? Really, the simplest way (and one we’ve done twice before) is to create a new exception subclass.
Also, the issue should not be characterized an “unintended exception”. The code raising the exception almost certainly intends to do so. Our problem is that a position search is also using the same exception type as a “not found” result. There is a big difference between “unintended” which sounds like noise that should suppressed and “ambiguous” which is an easily solvable problem.
Don’t we already have
IndexError for this?
Given a time machine, that would’ve been ideal (
KeyError both being subclasses of the often-overlooked
LookupError)… unfortunately our API contract is “
x is not found in
s.” (from Built-in Types — Python 3.11.4 documentation)
We could bridge the gap and add a multi-parent
class ValueIndexError(ValueError, IndexError) to be raised instead to avoid breaking compatibility while making it more clear what happened.
I’m not a big fan on inheritance diamonds though. I haven’t pondered if there could be noteworthy consequences of that.