Terry Reedy wrote:
On 10/4/2011 10:21 PM, Guido van Rossum wrote:
But with str.find, 0 is a legitimate result, so if we were to return None there'd be *two* outcomes mapping to false: no match, or a match at the start of the string, which is no good.
People would have to test that the result 'is None' or 'is not None'. That is no worse than testing '== -1' or '>= 0'. I claim it is better because continuing to compute with None as if it were a number will more likely quickly raise an error, whereas doing so with a '-1' that looks like a legitimate string position (the last), but does not really mean that, might never raise an error but lead to erroneous output. (I said 'more likely' because None is valid in slicings, same as -1.)
I'm out of ideas here. But of all these, str.find is probably still the worst -- I've flagged bugs caused by it too many times to count.
So lets deprecate it for eventual removal, maybe in Py4.
Although I stand by my earlier claim that "raise an exception" is the Obvious Way to deal with error conditions in Python, for some reason that logic doesn't seem to apply to searching. Perhaps because "not found" is not an error, it just feels uncomfortable, to many people. Whenever I use list.index, I always find myself missing list.find.
Perhaps it is because using try...except requires more effort. It just feels wrong to write (for example):
try: n = s.index('spam') except ValueError: pass else: s = s[n:]
n = s.find('spam') if n >= 0: s = s[n:]
This is especially a factor when using the interactive interpreter.
(I also wonder about the performance hit of catching an exception vs. testing the return code. In a tight loop, catching the exceptions may be costly.)
I don't think there is any perfect solution here, but allowing people the choice between index and find seems like a good plan to me. Using -1 as the not-found sentinel seems to be a mistake though, None would have been better. That None is valid in slices is actually a point in it's favour for at least two use-cases:
# extract everything after the substring (inclusive) # or the entire string if not found n = s.find('spam') substr = s[n:]
# extract everything before the substring (exclusive) # or the entire string if not found n = s.find('spam') substr = s[:n]
There are other cases, of course, but using None instead of -1 will generally give you an exception pretty quickly instead of silently doing the wrong thing.