Before I talk about the real topic, I have to ask if the discussion of enhancements should start here, because I already created a issue in the issue tracker.
It is Ok to just create it, or the discussion should start here?
Now the real topic:
When trying to search the position of an element inside a list, we should use the in operator to first check if the element exists, and then use the index method to obtain the index.
in(__contains__) runs a linear search to return the boolean. index also runs a linear search to return the index.
This makes the code slower, because we need to search for the same item twice.
Similar to str.find(), list.find() should be implemented, where -1 is returned when the element isn’t present
# Since there's no list.find(), this is my workaround to achieve making only one linear search per query
def find(container: list, index: int) -> int:
""" Str.find() behavior but for lists """
# Example driver code:
index = find(list, possible_element)
if index_of_element == -1:
pass # Not found
pass # Found
I think str.find() wouldn’t be added today if it didn’t already exist, because -1 is a valid index. Maybe you could argue that it should return None if the value isn’t found to prevent that error. But you’re still going to need some code to catch that, so an exception seems like the better design.
I think that I get it, this was my very main argument.
My view is that things should be consistent, searching for something in a list should not require a exception if searching it in a string does not (like, why???)
Also, I used to see exceptions as treatable errors
EOFError when trying to input() and STDIN stops
FileNotFoundError when trying to open invalid file
IndexError when index acess is out of bounds
SyntaxError if the syntax is incorrect
So I really don’t see how simply searching for a element in a list should require exception treatment, this looks like “not-so-good” design. How do this fit to the category of what I listed above?
This is so inconsistent, maybe it is just like that because it has always been like that.
But, that this is also not beginner-friendly, I bet a large amount of people are just using __contains__ followed by .index() because they don’t realized they need to catch the exception to avoid running a linear search twice.
When a function can’t return a reasonable value, it should raise an exception instead. This tends to result in safer programs: ones that fail cleanly, rather than calculating with incorrect values.
If a function returns a “error marker value” like None or -1 instead, any code that calls the function needs to check if it gets an “invalid value” marker. If you forget this, the program will behave in unexpected ways.
The find method was added long ago, and was (I presume) inspired by the C language. C doesn’t have exceptions, so most functions have a dedicated “error marker value” to return. If you look at some random module written in C, you’ll most likely find it has a lot of code that does essentially this:
(This is funniest when the operations are basic arithmetic…)
If you forget one of those error handling blocks, you have a bug. (Except if you can prove you can safely omit it, like the last one here. But be careful about errors in your reasoning!)
In Python, you’d need to surround every call to index with try/except, which would be just as bothersome as the C approach. But if you know the error case won’t happen, you can leave out the try/except, and the program will do “the right thing”. Not so in C: if you forget error handling in C, the program will ignore all erorrs (think MemoryError or a Ctrl+C interrupt).
Python’s idea of “the right thing” is raising exceptions: giving callers a chance to handle the error, and if none do, display a traceback with (hopefully) useful information about what went wrong.
(There are other ideas of “the right thing”: Rust or Go generally force the programmer to think about the possible errors every time something can go wrong, which can be as tedious as the C style, but since you can’t forget, it tends to lead to more robust programs than Python’s exceptions.)
The C way (which you can also have in Python – see find's -1 return value) is worst: it’s tedious and makes it easy to write buggy code. So we’re not likely to add new functions that act like this. On the other hand, we won’t remove functions that people already use: after all, it is possible to use find correctly, you just need to be a bit more careful. And it doesn’t really look tedious or bug-prone until you need to write a whole complex program in the C style – so it’s, sadly, not something you can easily explain to beginners :(
Maybe beginners’ courses should spend some time explaining that exceptions are friends, not monsters. Mine does. How can we make it more common?
If we can’t get rid of bytes.swapcase() or str.swapcase(), which I think are just about useless, then I don’t see us getting rid of str.find(), which at least has some valid uses. It’s just not worth the disruption.