Browse DevX
Sign up for e-mail newsletters from DevX


A Developer's Guide to Python 3.0: Core Language Changes : Page 4

In this deep comparison between Python 2.x and Python 3.0, discover the far-reaching changes to the Python core language, type system, and the standard library, how they'll affect your code, and guidelines for migration.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

PEP-3109 Raising Exceptions in Python 3000

In Python 2.X you can raise exceptions in two equivalent ways:

raise E, V raise E(V)

In both cases E is the exception class and V is the value. However, Python 3.0 allows only the second form. Python 2.X also supports one other (rare) way to raise an exception with an arbitrary traceback:

raise E, V, T

In Python 3.0 that becomes:

e = E(V) e.__traceback__ = T raise E

Although the Python 3.0 form is more cumbersome, because this form of raising exceptions is so rare (I can't think of a single use where I would want to attach an arbitrary traceback to an exception) that may not be important. If you want to re-raise the active exception inside an except or finally block, just continue using raise with no arguments.

Generators had an asynchronous facility for raising exceptions via the throw() method that accepted three arguments similar to the raise E, V, T form. In Python 3.0 throw() accepts only a single exception argument, so it's consistent with raise.

PEP-3110 Catching Exceptions in Python 3000

There are a few problems with the syntax and semantics of exception catching in Python 2.x. For example, to catch an exception type and its instance you use the following semantics:

except <expression_1>, <expression_2>:

If you needed to catch just a couple of exception types, you could wrap the types in parentheses:

except (<expression_1>, <expression_2>):

Unfortunately, that syntax can lead to some pretty nasty bugs if you forget the parentheses. In the following code snippet the except clause catches Exception and str (without parentheses). The intention is to catch either the general Exception class or the str type. The result is that only the Exception type is caught. The specific instance of Exception (DivisionInZeroException in this case) gets assigned to str. Trying to use str() later ends in an obscure and catastrophic fashion.

>>> try: ... 3 / 0 ... except Exception, str: ... print repr(str) ... ZeroDivisionError('integer division or modulo by zero',) >>> str(5) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'exceptions.ZeroDivisionError' object is not callable

If this exception-handling code happened to buried somewhere deep in your code, so that str() was used much later, it could be pretty difficult to locate.

Python 3.0 addresses these issues by replacing the first form (except TYPE, INSTANCE) with the following form:

except <exception type> as <instance>

In addition, the exception type must derive from exceptions.BaseException. If you want to catch multiple exception types you may still use the tuple syntax. Here's an example of how to catch exceptions in Python 3.0:

>>> try: ... 3 / 0 ... except Exception as e: ... print(repr(e)) ... ZeroDivisionError('int division or modulo by zero',) >>> try: ... 3 / 0 ... except (Exception, ZeroDivisionError): ... pass ...

Other changes related to exception catching are that the sys.exc_type, sys.exc_value, and sys.exc_traceback have been removed. The information is available as the tuple returned from sys.exc_info():

>>> try: ... 3/0 ... except: ... print(sys.exc_info()) ... (<class 'ZeroDivisionError'>, ZeroDivisionError('int division or modulo by zero',), <traceback object at 0x3bcbc0>)

Even sys.exc_info() may go away in the future. Alternatives like exception attributes were discussed—but dropped due to the difficulties involved in auto-fixing existing code. The traceback has become an attribute as you will see in the next section.

Finally, Python 3.0 will make sure that the exception instance gets deleted at the end of the except clause to avoid garbage collection issues.

PEP-3134 Exception Chaining and Embedded Tracebacks

Exception objects in Python 3.0 acquire three new attributes: __context__, __cause__, and __traceback__. The __context__ attribute is useful when you explicitly raise an exception that was originally raised during the handling of another exception. In Python 2.x the original exception was lost and replaced by the second exception as shown below:

>>> try: ... raise Exception('first') ... except: ... raise Exception('second') ... Traceback (most recent call last): File "<stdin>", line 4, in <module> Exception: second

Here's the same code running in Python 3.0:

>>> try: ... raise Exception('first') ... except: ... raise Exception('second') ... Traceback (most recent call last): File "<stdin>", line 2, in <module> Exception: first During handling of the above exception, another exception occurred: Traceback (most recent call last): File "<stdin>", line 4, in <module> Exception: second

As you can see the second exception object stores the original exception object in its __context__ attribute:

>>> e.__context__ Exception('first',) >>> type(e.__context__) <class 'Exception'>

This process of exception chaining can go on and on if you have nested try-except blocks. Each exception stores the preceding exception in its __context__.

The __cause__ attribute is useful when you want to explicitly translate one exception to another. The syntax is: raise EXCEPTION from CAUSE. CAUSE must be an exception too. Here's an example:

try: raise Exception('duh!') from Exception('Gigi') except Exception as e: assert e.__context__ is None assert repr(e.__cause__) == "Exception('Gigi',)"

I don't really understand why this syntax is needed. The from CAUSE is just syntactic sugar, because (except that CAUSE will be stored in __context__) it's equivalent to:

try: try: raise CAUSE except: raise EXCEPTION

The fact that in one form the original exception is stored in __context__ and in the other form it's stored in __cause__ might lead to confusing and complex error handling code where you have to check not only the caught exception, but also the __cause__ and the __context__ because you can't be sure what to expect. I can accept the syntactic sugar of from CAUSE, but I think CAUSE should just be stored in __context__.

The last new Exception attribute is __traceback__, which is a traceback object that tells you the exception origin. In Python 2.x you had to call sys.exc_info()[2] or sys.exc_traceback to get the traceback object. Now, it's an attribute of the exception object itself, which makes it more discoverable and the code more concise. Remember to use the traceback module to make sense of traceback objects:

import traceback def f(): g() def g(): raise Exception('nested') t = None try: f() except Exception as e: t = e.__traceback__ print('----------------') print('The stack trace') print('----------------') traceback.print_tb(t) print('----------------') ---------------- The stack trace ---------------- File "article_1.py", line 244, in <module> f() File "article_1.py", line 237, in f g() File "article_1.py", line 240, in g raise Exception('nested') ----------------

This first article in the "Developer's Guide to Python 3.0" series provided information about the important changes to the core language and its type system. The next article in the series focuses on how Python 3.0 treats the basic data types: numbers, text and binary data.

Gigi Sayfan specializes in cross-platform object-oriented programming in C/C++/C#/Python/Java with an emphasis on large-scale distributed systems. He is currently trying to build brain-inspired intelligent machines at Numenta.
Thanks for your registration, follow us on our social networks to keep up-to-date