Exception handling in Python 3.x

Steven D'Aprano steve+comp.lang.python at pearwood.info
Fri Dec 3 08:04:26 EST 2010


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:

>>> func([])
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:

>>> func([])
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?



-- 
Steven



More information about the Python-list mailing list