On Fri, Feb 21, 2014 at 12:38:01AM +0200, אלעזר wrote:
2014-02-21 0:00 GMT+02:00 Steven D'Aprano <steve@pearwood.info>:
On Thu, Feb 20, 2014 at 04:14:17PM +0000, Oscar Benjamin wrote:
On 20 February 2014 16:05, Terry Reedy <tjreedy@udel.edu> wrote:
An implementation of first() should raise some other exception than StopIteration.
[...]
It's easy enough to do if you know that bare next is a bad thing.
Say what? Why do you say that next(it) is a bad thing?
More-itertools does it the way I would but has a long comment wondering whether it should actually raise StopIteration:
https://github.com/erikrose/more-itertools/blob/master/more_itertools/more.p...
The thing is just that bare next is not something that's widely recognised as being dangerous. I've seen examples of this kind of bug in samples from many Python aficionados (including at least one from you Terry).
Why is it dangerous, and what kind of bug?
If you're talking about the fact that next(it) can raise StopIteration, I think you are exaggerating the danger. Firstly, quite often you don't mind if it raises StopIteration, since that's what you would have done anyway. Secondly, I don't see why raising StopIteration is so much more dangerous than (say) IndexError or KeyError.
I had this bug just the other day. I did not plan for the empty case, since it was obvious that the empty case is a bug, so I relied on the exception being raised in this case. But I did not get the exception since it was caught in a completely unrelated for loop. It took me a while to figure out what's going on, and it would've taken even more for someone else, not familiar with my assumption or with the whole StopIteration thing (which I believe is the common case). An IndexError or a KeyError would have been great in such a case.
IndexError can also be caught by for-loops, under some circumstances. (See the legacy sequence protocol for iteration.) Without knowing what your code does, and how it is written, it's hard to discuss in anything but generalities. But in general, any code that is expected to raise an exception should have a test that it actually does raise that exception. If you're raising an exception that has particular meaning to Python, then you have to be prepared that Python might intercept it before you can see it. Would you be surprised by this? py> class X: ... def method(self): ... # Perform some calculation, which fails. ... raise AttributeError ... def __getattr__(self, name): ... if name == 'spam': return self.method() ... py> x = X() py> getattr(x, 'spam', 23) 23 If the only place you are using x.spam is inside a getattr call, then you will never see the AttributeError. Likewise, if the only place you are using next() is inside a for-loop iterable, then the StopIteration exception will always be captured. (This may even be deliberate on the part of the programmer.) I don't see this as particularly noteworthy. It's one of the things that people probably won't think of until they've actually seen it happen, but programming is full of things like that. Not every tricky bug to find is a sign that the function is "dangerous". -- Steven