On Tue, Nov 25, 2014 at 9:48 AM, Chris Angelico rosuav@gmail.com wrote:
On Wed, Nov 26, 2014 at 4:30 AM, Wolfgang Maier wolfgang.maier@biologie.uni-freiburg.de wrote:
Well, I'm not familiar with every implementation detail of the
interpreter
so I can't judge how difficult to implement certain things would be, but
one
solution that I could think of is:
I don't know much about the internal details of CPython either, but let's just ignore that for the moment and consider specs for the Python language. AFAIK, not one of the concerns raised (by PEP 479 or your proposal here) is CPython-specific.
allow StopIteration to be raised anywhere, but let it bubble up only
*one*
frame. So if the next outer frame does not deal with it, the exception would be converted to UnhandledStopIteration (or something else) when it's about
to
bubble out of that outer frame. The builtin next() would simply reset the frame count by catching and reraising StopIteration raised inside its argument (whether that's an iterator's __next__ or a generator; note that in this scenario using
raise
StopIteration instead of return inside a generator would remain
possible).
Interesting. Makes a measure of sense, and doesn't have much magic to it.
So different from the current PEP where a StopIteration must be dealt
with
explicitly using try/except only inside generators, but bubbles up everywhere else, here StopIteration will be special everywhere, i.e., it must be passed upwards explicitly through all frames or will get
converted.
Back to Steven's generator expression vs comprehension example:
iterable = [iter([])] list(next(x) for x in iterable)
would raise UnhandledStopIteration since there is no way, inside the generator expression to catch the StopIteration raised by next(x).
Downside of this is that it's harder to consciously chain iterators, but maybe that's a cost that has to be paid.
Suggestion for this: Have a new way of "raise-and-return". It's mostly like raise, except that (a) it can't be caught by a try/except block in the current function (because there's no point), and (b) it bypasses the "this exception must not pass unnoticed". It could then also be used for anything else that needs the "return any object, or signal lack of return value" option, covering AttributeError and so on.
So it'd be something like this:
class X: def __iter__(self): return self def __next__(self): if condition: return value signal StopIteration
The 'signal' statement would promptly terminate the function (not sure exactly how it'd interact with context managers and try/finally, but something would be worked out), and then raise StopIteration in the calling function. Any other StopIteration which passes out of a function would become a RuntimeError.
Magic required: Some way of knowing which exceptions should be covered by this ban on bubbling; also, preferably, some way to raise StopIteration in the calling function, without losing the end of the backtrace.
This could be a viable proposal. It'd be rather more complicated than PEP 479, though, and would require a minimum of five hundred bikeshedding posts before it comes to any kind of conclusion, but if you feel this issue is worth it, I'd certainly be an interested participant in the discussion.
It's not viable. It will break more code than PEP 479, and it will incur a larger interpreter overhead (every time any exception bubbles out of any frame we'd have to check whether it is (derived from) StopIteration and replace it, rather than only when exiting a generator frame. (The check for StopIteration is relatively expensive -- it's easy to determine that an exception *is* StopIteration, but in order that it doesn't derive from StopIteration you have to walk the inheritance tree.)
Please stop panicking.