S
Steven D'Aprano
Consider the following common exception handling idiom:
def func(iterable):
it = iter(iterable)
try:
x = next(it)
except StopIteration:
raise ValueError("can't process empty iterable")
print(x)
The intention is:
* detect an empty iterator by catching StopIteration;
* if the iterator is empty, raise a ValueError;
* otherwise process the iterator.
Note that StopIteration is an internal detail of no relevance whatsoever
to the caller. Expose this is unnecessary at best and confusing at worst.
In Python 2.6 this idiom works as intended:
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in func
ValueError: can't process empty iterable
There is no sign of the StopIteration, and nor should there be.
But Python 3.1 changes this behaviour, exposing the unimportant
StopIteration and leading to a more complicated and confusing traceback:
File "<stdin>", line 4, in func
StopIteration
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in func
ValueError: can't process empty iterable
I understand the rational of this approach -- it is to assist in
debugging code where the except block is buggy and raises an error. But a
deliberate and explicit call to raise is not a buggy except block. It is
terribly inappropriate for the common use-case of catching one exception
and substituting another.
I note that the PEP explicitly notes this use-case, but merely sweeps it
under the carpet:
Open Issue: Suppressing Context
As written, this PEP makes it impossible to suppress '__context__',
since setting exc.__context__ to None in an 'except' or 'finally'
clause will only result in it being set again when exc is raised.
http://www.python.org/dev/peps/pep-3134/
Apart from this horrible idiom:
def func(iterable):
it = iter(iterable)
failed = False
try:
x = next(it)
except StopIteration:
failed = True
if failed:
raise ValueError("can't process empty iterable")
print(x)
or similar, is there really no way to avoid these chained exceptions?
def func(iterable):
it = iter(iterable)
try:
x = next(it)
except StopIteration:
raise ValueError("can't process empty iterable")
print(x)
The intention is:
* detect an empty iterator by catching StopIteration;
* if the iterator is empty, raise a ValueError;
* otherwise process the iterator.
Note that StopIteration is an internal detail of no relevance whatsoever
to the caller. Expose this is unnecessary at best and confusing at worst.
In Python 2.6 this idiom works as intended:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in func
ValueError: can't process empty iterable
There is no sign of the StopIteration, and nor should there be.
But Python 3.1 changes this behaviour, exposing the unimportant
StopIteration and leading to a more complicated and confusing traceback:
Traceback (most recent call last):
File "<stdin>", line 4, in func
StopIteration
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in func
ValueError: can't process empty iterable
I understand the rational of this approach -- it is to assist in
debugging code where the except block is buggy and raises an error. But a
deliberate and explicit call to raise is not a buggy except block. It is
terribly inappropriate for the common use-case of catching one exception
and substituting another.
I note that the PEP explicitly notes this use-case, but merely sweeps it
under the carpet:
Open Issue: Suppressing Context
As written, this PEP makes it impossible to suppress '__context__',
since setting exc.__context__ to None in an 'except' or 'finally'
clause will only result in it being set again when exc is raised.
http://www.python.org/dev/peps/pep-3134/
Apart from this horrible idiom:
def func(iterable):
it = iter(iterable)
failed = False
try:
x = next(it)
except StopIteration:
failed = True
if failed:
raise ValueError("can't process empty iterable")
print(x)
or similar, is there really no way to avoid these chained exceptions?