On Tue, 2011-10-04 at 19:21 -0700, Guido van Rossum wrote:
On Tue, Oct 4, 2011 at 4:13 PM, Steven D'Aprano email@example.com wrote:
Carl Matthew Johnson wrote:
This reminds me of the string.index vs. string.find discussion we had a while back. In basically any situation where an exception can be raised, it's sometimes nice to return a None-like value and sometimes nice to have an out-of-band exception. I have a certain amount of admiration for the pattern in Go of returning (value, error) from most functions that might have an error, but for Python as it is today, there's no One Obvious Way to Do It yet, and there's probably none forthcoming.
I beg to differ. Raising an exception *is* the One Obvious Way in Python. But OOW does not mean "Only One Way", and the existence of raise doesn't mean that there can't be a Second Not-So-Obvious Way, such as returning a "not found" value.
However, returning None as re.match does is better than returning -1 as str.find does, as -1 can be mistaken for a valid result but None can't be.
What works for re.match doesn't work for str.find. With re.match, the result when cast to bool is true when there's a match and false when there isn't. That's elegant.
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. Hence the -1: the intent was that people should write "if s.find(x) >= 0" -- but clearly that didn't work out either, it's too easy to forget the ">= 0" part. We also have str.index which raised an exception, but people dislike writing try/except blocks. We now have "if x in s" for situations where you don't care where the match occurred, but unfortunately if you need to check whether *and* where a match occurred, your options are str.find (easy to forget the ">= 0" part), str.index (cumbersome to write the try/except block), or "if x in s: i = s.index(x); ..." which looks compact but does a redundant second linear search. (It is also too attractive since it can be used without introducing a variable.)
Other ideas: returning some more structured object than an integer (like re.match does) feels like overkill, and returning an (index, success) tuple is begging for lots of mysterious occurrences of  or .
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.
There is also the newer partition and rpartition methods, which I tend to forget about.
I really don't like the '-1' for a not found case. They just get in the way.
If len(s) was the not found case, you get a value that can be used in a slice without first checking the index, or catching an exception.
Lets say we didn't have a split method and needed to write one.
If s.find returned len(s) as the not found...
def split(s, x): result =  start = 0 while start < len(s): i = s.find(x, start) result.append(s[start:i]) # No check needed here. start = i + len(x) return result
Of course you could you this same pattern for other things.