Python 2.x和3.x中引发异常的有效语法是什么?

10
我该如何将这段代码移植到Python 3,以便它可以在Python 2和Python 3中运行?
raise BarException, BarException(e), sys.exc_info()[2]

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

奖励问题
这样做有意义吗?

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")
2个回答

11

Python 2 / 3兼容代码抛出异常

Six提供了简单的实用程序,用于处理Python 2和Python 3之间的差异。它旨在支持能够在Python 2和3上运行而无需修改的代码库。six只包含一个Python文件,因此将其复制到项目中非常容易。 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)

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:

$ 2to3 -w example.py

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

Python版本确定

如果您想确定Python的版本,我建议使用以下方法:

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时引发异常。

# 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是Python 2和Python 3之间缺失的兼容性层。它允许您使用单个清洁的Python 3.x兼容代码库来支持Python 2和Python 3,开销最小。

它提供了未来和过去的包,并从Python 3和2中回退和前向端口功能。它还配备了futurize和pasteurize,这是定制的基于2to3的脚本,可以帮助您轻松地将Py2或Py3代码转换为单个干净的Py3-style代码库,逐个模块。 http://python-future.org/overview.html

请参阅Python的future模块文档http://python-future.org/。以下是页面上Raising Excettions和Cathcing Exceptions部分的副本。

引发异常

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

仅适用于Python 2:

raise ValueError, "dodgy value"

Python 2和3:

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

仅适用于Python 2:

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

仅适用于Python 3:

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

Python 2和3:选项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和3:选项2


from future.utils import raise_with_traceback

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

设置:

class DatabaseError(Exception):
    pass

仅适用于Python 3

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:

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)

测试以上内容:

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:

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

Python 2 和 3:

try:
    ...
except ValueError as e:
    ...

9

由于Python 3不能使用三个参数的语法,所以您将不得不使用exec()。否则会引发语法错误。

一如既往,six已经为您准备好了,不依赖其他six定义的版本如下:

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")

现在您可以使用:
reraise(BarException, BarException(e), sys.exc_info()[2])

不需要进一步测试Python版本。


这看起来像是一个很棒的答案。我自己也做了一些研究,找到了一种解决方法,就是像这样:raise ConnectionError(BarException(e)).with_traceback(sys.exc_info()[2])(取自于这里:http://www.diveintopython3.net/porting-code-to-python-3-with-2to3.html#raise),请问这样做是否会带来不好的编程风格? - speendo
1
@speendo:Python 2的异常没有with_traceback()方法,因此您无法在多语言代码中使用它。 - Martijn Pieters
1
您所参考的页面仅展示了使用“2to3”工具将Python 2代码移植到Python 3时会发生什么,它并没有告诉您如何编写适用于两个版本的代码。 - Martijn Pieters

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接