Valid syntax in both Python 2.x and 3.x for raising exception?

10,545

Solution 1

You'll have to resort to using exec() because you cannot use the 3-argument syntax in Python 3; it'll raise a syntax error.

As always the six library already has you covered, ported to not depend on other six definitions their version looks like this:

import sys

if sys.version_info[0] == 3:
    def reraise(tp, value, tb=None):
        if value is None:
            value = tp()
        if value.__traceback__ is not tb:
            raise value.with_traceback(tb)
        raise value

else:    
    exec("def reraise(tp, value, tb=None):\n    raise tp, value, tb\n")

Now you can use:

reraise(BarException, BarException(e), sys.exc_info()[2])

without further testing for a Python version.

Solution 2

Python 2 / 3 compatible code to raise an exception

Six provides simple utilities for wrapping over differences between Python 2 and Python 3. It is intended to support codebases that work on both Python 2 and 3 without modification. six consists of only one Python file, so it is painless to copy into a project. http://pythonhosted.org/six/

from six import reraise as raise_  # or from future.utils import raise_
traceback = sys.exc_info()[2]
err_msg = "Bar is closed on Christmas"
raise_(ValueError, err_msg, traceback)

Conversion from Python 2 to Python 3.

You can make a Python 3 copy of the code using 2to3.

2to3 is a Python program that reads Python 2.x source code and applies a series of fixers to transform it into valid Python 3.x code. The standard library contains a rich set of fixers that will handle almost all code. 2to3 supporting library lib2to3 is, however, a flexible and generic library, so it is possible to write your own fixers for 2to3. lib2to3 could also be adapted to custom applications in which Python code needs to be edited automatically.

...

2to3 can also write the needed modifications right back to the source file. (Of course, a backup of the original is also be made unless -n is also given.) Writing the changes back is enabled with the -w flag:

$ 2to3 -w example.py

(from https://docs.python.org/3.0/library/2to3.html)

Python version determination

If you want to determine the python version, I recommend:

PY2 = sys.version_info.major == 2
PY3 = sys.version_info.major == 3
# or
import six  # Python 2 / 3 compatability module
six.PY2     # is this Python 2
six.PY3     # is this Python 3

Python decisions based on version

Don't forget that early versions of Python 2 will vary from 2.7. I like to plan for all contingencies, so the following code takes exception (litteraly) if a Python version prior to 2.7 is used.

# If you want to use and if/then/else block...
import sys
major = sys.version_info.major
minor = sys.version_info.minor
if major == 3:     # Python 3 exception handling
    print("Do something with Python {}.{} code.".format(major, minor))
elif major == 2:   # Python 2 exception handling
    if minor >= 7:     # Python 2.7
        print("Do something with Python {}.{} code.".format(major, minor))
    else:   # Python 2.6 and earlier exception handling
        assert minor >= 2, "Please use Python 2.7 or later, not {}.{}.".format(major,minor)
else:
    assert major >= 2, "Sorry, I'm not writing code for pre-version 2 Python.  It just ain't happening.  You are using Python {}.{}.".format(major,minor)
    assert major > 3, "I can't handle Python versions that haven't been written yet..  You are using Python {}.{}.".format(major,minor)

Exception Handling in Python 2 and 3

python-future is the missing compatibility layer between Python 2 and Python 3. It allows you to use a single, clean Python 3.x-compatible codebase to support both Python 2 and Python 3 with minimal overhead.

It provides future and past packages with backports and forward ports of features from Python 3 and 2. It also comes with futurize and pasteurize, customized 2to3-based scripts that helps you to convert either Py2 or Py3 code easily to support both Python 2 and 3 in a single clean Py3-style codebase, module by module. http://python-future.org/overview.html

See python's future module documentation at http://python-future.org/. Below is a copy of the Raising Excettions and Cathcing Exceptions portions of the page.

Raising Exceptions

import future        # pip install future
import builtins      # pip install future
import past          # pip install future
import six           # pip install six

Python 2 only:

raise ValueError, "dodgy value"

Python 2 and 3:

raise ValueError("dodgy value")
Raising exceptions with a traceback:

Python 2 only:

traceback = sys.exc_info()[2]
raise ValueError, "dodgy value", traceback

Python 3 only:

raise ValueError("dodgy value").with_traceback()

Python 2 and 3: option 1

from six import reraise as raise_
# or
from future.utils import raise_

traceback = sys.exc_info()[2]
raise_(ValueError, "dodgy value", traceback)

Python 2 and 3: option 2

from future.utils import raise_with_traceback

raise_with_traceback(ValueError("dodgy value"))
Exception chaining (PEP 3134):

Setup:

class DatabaseError(Exception):
    pass

Python 3 only

class FileDatabase:
    def __init__(self, filename):
        try:
            self.file = open(filename)
        except IOError as exc:
            raise DatabaseError('failed to open') from exc

Python 2 and 3:

from future.utils import raise_from

class FileDatabase:
    def __init__(self, filename):
        try:
            self.file = open(filename)
        except IOError as exc:
            raise_from(DatabaseError('failed to open'), exc)

Testing the above:

try:
    fd = FileDatabase('non_existent_file.txt')
except Exception as e:
    assert isinstance(e.__cause__, IOError)    # FileNotFoundError on Py3.3+ inherits from IOError

Catching exceptions

Python 2 only:

try:
    ...
except ValueError, e:
    ...

Python 2 and 3:

try:
    ...
except ValueError as e:
    ...
Share:
10,545
speendo
Author by

speendo

Updated on July 24, 2022

Comments

  • speendo
    speendo almost 2 years

    How can I port this code to Python 3 so that it would run in both, Python 2 and Python3?

    raise BarException, BarException(e), sys.exc_info()[2]
    

    (copied from http://blog.ionelmc.ro/2014/08/03/the-most-underrated-feature-in-python-3/)

    Bonus question
    Does it make sense to do something like

    IS_PYTHON2 = sys.version_info < (3, 0)
    
    if IS_PYTHON2:
        raise BarException, BarException(e), sys.exc_info()[2]
        # replace with the code that would run in Python 2 and Python 3 respectively
    else:
        raise BarException("Bar is closed on Christmas")
    
  • speendo
    speendo over 8 years
    This looks like a brilliant answer. I did some research myself and just found a solution to do it like this: raise ConnectionError(BarException(e)).with_traceback(sys.exc_info‌​()[2]) (taken from here: diveintopython3.net/…) is this bad style?
  • Martijn Pieters
    Martijn Pieters over 8 years
    @speendo: Python 2 exceptions don't have the with_traceback() method, so you can't use that in polyglot code.
  • Martijn Pieters
    Martijn Pieters over 8 years
    The page you are referring to only shows what happens when using the 2to3 tool to port Python 2 to Python 3 code, it doesn't tell you how to write code that works in both versions.