Python 2.x 和 3.x 中用于引发异常的有效语法?

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

如何将此代码移植到 Python 3 以便它可以在 Python 2 和 Python3 中运行?

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

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

奖金问题
做类似

的事情有意义吗

1
2
3
4
5
6
7
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")

引发异常的 Python 2 / 3 兼容代码

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/

1
2
3
4
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)

从 Python 2 到 Python 3 的转换。

您可以使用 2to3 制作代码的 Python 3 副本。

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:

1
$ 2to3 -w example.py

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

Python版本确定

如果要确定python版本,我推荐:

1
2
3
4
5
6
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 决策

不要忘记 Python 2 的早期版本与 2.7 不同。我喜欢为所有意外情况做好计划,因此如果使用 2.7 之前的 Python 版本,以下代码将出现异常(字面意思)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 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)

Python 2 和 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

请参阅 http://python-future.org/ 上的 python\\'s future 模块文档。
以下是该页面的"提高异常"和"排除异常"部分的副本。

引发异常

1
2
3
4
import future        # pip install future
import builtins      # pip install future
import past          # pip install future
import six           # pip install six

仅限 Python 2:

1
raise ValueError,"dodgy value"

Python 2 和 3:

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

仅限 Python 2:

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

仅限 Python 3:

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

Python 2 和 3:选项 1

1
2
3
4
5
6
from six import reraise as raise_
# or
from future.utils import raise_

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

Python 2 和 3:选项 2

1
2
3
4
from future.utils import raise_with_traceback

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

设置:

1
2
class DatabaseError(Exception):
    pass

仅限 Python 3

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

Python 2 和 3:

1
2
3
4
5
6
7
8
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)

测试上述内容:

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

捕捉异常

仅限 Python 2:

1
2
3
4
try:
    ...
except ValueError, e:
    ...

Python 2 和 3:

1
2
3
4
try:
    ...
except ValueError as e:
    ...

你将不得不使用 exec() 因为你不能在 Python 3 中使用 3-argument 语法;它会引发语法错误。

一如既往,six 库已经涵盖了您,移植到不依赖于其他 six 定义,它们的版本如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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):\
    raise tp, value, tb\
"
)

现在你可以使用:

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

无需进一步测试 Python 版本。