Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 3109 – Raising Exceptions in Python 3000

Author:
Collin Winter <collinwinter at google.com>
Status:
Final
Type:
Standards Track
Created:
19-Jan-2006
Python-Version:
3.0
Post-History:


Table of Contents

Abstract

This PEP introduces changes to Python’s mechanisms for raising exceptions intended to reduce both line noise and the size of the language.

Rationale

One of Python’s guiding maxims is “there should be one – and preferably only one – obvious way to do it”. Python 2.x’s raise statement violates this principle, permitting multiple ways of expressing the same thought. For example, these statements are equivalent:

raise E, V

raise E(V)

There is a third form of the raise statement, allowing arbitrary tracebacks to be attached to an exception [1]:

raise E, V, T

where T is a traceback. As specified in PEP 344, exception objects in Python 3.x will possess a __traceback__ attribute, admitting this translation of the three-expression raise statement:

raise E, V, T

is translated to

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

Using these translations, we can reduce the raise statement from four forms to two:

  1. raise (with no arguments) is used to re-raise the active exception in an except suite.
  2. raise EXCEPTION is used to raise a new exception. This form has two sub-variants: EXCEPTION may be an exception class or an instance of an exception class; valid exception classes are BaseException and its subclasses (PEP 352). If EXCEPTION is a subclass, it will be called with no arguments to obtain an exception instance.

    To raise anything else is an error.

There is a further, more tangible benefit to be obtained through this consolidation, as noted by A.M. Kuchling [2].

PEP 8 doesn't express any preference between the
two forms of raise statements:
raise ValueError, 'blah'
raise ValueError("blah")

I like the second form better, because if the exception arguments
are long or include string formatting, you don't need to use line
continuation characters because of the containing parens.

The BDFL has concurred [3] and endorsed the consolidation of the several raise forms.

Grammar Changes

In Python 3, the grammar for raise statements will change from [1]

raise_stmt: 'raise' [test [',' test [',' test]]]

to

raise_stmt: 'raise' [test]

Changes to Builtin Types

Because of its relation to exception raising, the signature for the throw() method on generator objects will change, dropping the optional second and third parameters. The signature thus changes (PEP 342) from

generator.throw(E, [V, [T]])

to

generator.throw(EXCEPTION)

Where EXCEPTION is either a subclass of BaseException or an instance of a subclass of BaseException.

Semantic Changes

In Python 2, the following raise statement is legal

raise ((E1, (E2, E3)), E4), V

The interpreter will take the tuple’s first element as the exception type (recursively), making the above fully equivalent to

raise E1, V

As of Python 3.0, support for raising tuples like this will be dropped. This change will bring raise statements into line with the throw() method on generator objects, which already disallows this.

Compatibility Issues

All two- and three-expression raise statements will require modification, as will all two- and three-expression throw() calls on generators. Fortunately, the translation from Python 2.x to Python 3.x in this case is simple and can be handled mechanically by Guido van Rossum’s 2to3 utility [4] using the raise and throw fixers ([5], [6]).

The following translations will be performed:

  1. Zero- and one-expression raise statements will be left intact.
  2. Two-expression raise statements will be converted from
    raise E, V
    

    to

    raise E(V)
    

    Two-expression throw() calls will be converted from

    generator.throw(E, V)
    

    to

    generator.throw(E(V))
    

    See point #5 for a caveat to this transformation.

  3. Three-expression raise statements will be converted from
    raise E, V, T
    

    to

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

    Three-expression throw() calls will be converted from

    generator.throw(E, V, T)
    

    to

    e = E(V)
    e.__traceback__ = T
    generator.throw(e)
    

    See point #5 for a caveat to this transformation.

  4. Two- and three-expression raise statements where E is a tuple literal can be converted automatically using 2to3’s raise fixer. raise statements where E is a non-literal tuple, e.g., the result of a function call, will need to be converted manually.
  5. Two- and three-expression raise statements where E is an exception class and V is an exception instance will need special attention. These cases break down into two camps:
    1. raise E, V as a long-hand version of the zero-argument raise statement. As an example, assuming F is a subclass of E
      try:
          something()
      except F as V:
          raise F(V)
      except E as V:
          handle(V)
      

      This would be better expressed as

      try:
          something()
      except F:
          raise
      except E as V:
          handle(V)
      
    2. raise E, V as a way of “casting” an exception to another class. Taking an example from distutils.compiler.unixcompiler
      try:
          self.spawn(pp_args)
      except DistutilsExecError as msg:
          raise CompileError(msg)
      

      This would be better expressed as

      try:
          self.spawn(pp_args)
      except DistutilsExecError as msg:
          raise CompileError from msg
      

      Using the raise ... from ... syntax introduced in PEP 344.

Implementation

This PEP was implemented in revision 57783 [7].

References


Source: https://github.com/python/peps/blob/main/pep-3109.txt

Last modified: 2022-01-21 11:03:51 GMT