For example, consider this function:
def foo():
for i in range(3):
print(i)
There is no try-except in this code, and neither in its bytecode. So this is not affected by the zero cost optimization from the linked issue.The bytecode is this:
2 0 LOAD_GLOBAL 0 (range)
2 LOAD_CONST 1 (3)
4 CALL_FUNCTION 1
6 GET_ITER
>> 8 FOR_ITER 12 (to 22)
10 STORE_FAST 0 (i)
3 12 LOAD_GLOBAL 1 (print)
14 LOAD_FAST 0 (i)
16 CALL_FUNCTION 1
18 POP_TOP
20 JUMP_ABSOLUTE 8
>> 22 LOAD_CONST 0 (None)
24 RETURN_VALUE
The iteration logic is inside the `FOR_ITER` op. And that is purely handled inside CPython.Also, this is again what I meant: In user code, you rarely would use custom exceptions for control flow. Please show me any example where you have seen otherwise.
So, again, what I wrote: I'm very confident that for most user code, the code path under `except ...:` is only rarely executed. Please show me any big Python project where this would not be the case. I doubt that there is any.
Also, btw, on EAFP, Guido van Rossum disagrees: https://mail.python.org/pipermail/python-dev/2014-March/1331...
That's...horrifying, both from a computational efficiency perspective (due to the overhead of exceptions) and from a "beauty" perspective (exceptions are supposed to be exceptional, darn it!). Why is it implemented this way? Why not just use a normal loop and check a return value from the iterator?
This feeling is at odds with what is generally considered idiomatic Python, for better or worse.
Upthread BiteCode mentioned EAFP. See, for example: https://devblogs.microsoft.com/python/idiomatic-python-eafp-...
(It's certainly not unanimous. Guido himself is on the record as saying EAFP isn't better than LBYL, but in most of the Python I've cruised through these last umpteen years [that wasn't some other language written in Python], I've certainly seen an overall preference for EAFP. Naturally YMMV.)
Because an iterator needs to be able to return any python value, and if you did that, there would be at least one python value that an iterator could not actually return.
Unless you required the iterator to wrap real return values in a container (basically, an optional/maybe monad) but then you have unwrapping overhead on every real return value, which is worse.
You do that with two or more of the protocols that Python has that use return values for return values ans exceptions for flow control signals and...you make it a lot harder to work with them, especially in conjunction.
Also, EAFP, and the context manager protocol.