
On Dec 9, 2019, at 18:23, Juancarlo Añez <apalala@gmail.com> wrote:
So while 1-arg next() and the try/except StopIteration pattern are essential and well-known, 2-arg next() is relatively esoteric and consequently (I think) not nearly as well-known.
And knowing that you must use iter() for 2-arg next() to (maybe) work right is idiosyncratic.
It takes a "Python historian" to understand why it may be correct to use:
the_first_item_if_ordered = next(iter(container), default='not found')
Why “may be correct”? It’s always correct. You can always call iter on any Iterable, you can always call next on the result with a default, so this always works. And you don’t need to be a Python historian to know why it works; it follows directly from the documentation of the two functions and the meaning of Iterable. (You may need to be a Python historian to understand why people often don’t remember this and therefore don’t use it, but that seems like the kind of thing you’d expect to go to a historian for.)
While the semantics of first() (whichever the chosen implementation) are straightforward to explain:
one_item_if_any = first(return_a_set(), default=-1)
or: the_first_item = first(sorted(container))
But they both work exactly as well with next: one_item_if_any = next(iter(return_a_set()), default=-1) That’s exactly what first means, and the doc string for the more_itertools version even directly tells you that it’s just a slightly shorter way to write the same thing. If the argument for first is that it can do things you can’t do otherwise, or that there’s some subtle and complicated case in which next may not work that only historians can understand, the argument is just wrong. The real argument for first is that it’s (hopefully) more discoverable than 2-arg next. (That, and Tim’s argument that we should lower the bar for inclusion in itertools.)
I agree with others in that the "default" argument should be explicit instead of implied. It's how dict.get(), and dict.pop(), etc., work. The exceptions raised when nothing can be returned from first() and there is no default= should be the same.
KeyError? Why? I think the ValueError suggested by many people in this thread (and used in multiple places in more_itertools) makes more sense. Trying to get the first value out of an empty Iterable is a lot like trying to use tuple unpacking on an empty Iterable; it’s not much like trying to look up a key in an empty dict.