如何编写兼容Python 2和Python 3的异常重新引发代码?

24 投票
2 回答
6059 浏览
提问于 2025-04-17 13:31

我正在尝试让我的WSGI服务器实现能够同时兼容Python 2和Python 3。我之前有这样的代码:

def start_response(status, response_headers, exc_info = None):
    if exc_info:
        try:
            if headers_sent:
                # Re-raise original exception if headers sent.
                raise exc_info[0], exc_info[1], exc_info[2]
        finally:
            # Avoid dangling circular ref.
            exc_info = None
    elif headers_set:
        raise AssertionError("Headers already set!")

    headers_set[:] = [status, response_headers]
    return write

...相关的部分是:

# Re-raise original exception if headers sent.
raise exc_info[0], exc_info[1], exc_info[2]

但是Python 3不再支持那种写法,所以必须改成:

raise exc_info[0].with_traceback(exc_info[1], exc_info[2])

问题是:Python 2的写法在Python 3中会产生解析错误。我该怎么写代码才能让Python 2和Python 3都能理解呢?我试过以下方法,但都不行:

if sys.version_info[0] >= 3:
    raise exc_info[0].with_traceback(exc_info[1], exc_info[2])
else:
    eval("raise exc_info[0], exc_info[1], exc_info[2]; 1", None, { 'exc_info': exc_info })

2 个回答

51

你可以使用 six 吗?它就是为了解决这个问题而存在的。

import six, sys
six.reraise(*sys.exc_info())

查看: https://six.readthedocs.io/index.html#six.reraise

0

你可以尝试一些创意的做法。

在你代码的开头,比如在构造函数那里,检查一下你使用的Python版本。因为你平常用的版本检查方法不管用了,可以试试这个:

try:
  eval('a python 3 expression') # something that only works in python3+
  python_version = 3
except:
  python_version = 2

然后你代码的其他部分就可以很方便地引用这个,知道该用什么。

至于解析错误,你可以在一个函数里使用exec,像这样:

def what_to_run():
    if python_version = 3:
        return 'raise exc_info[0].with_traceback(exc_info[1], exc_info[2])'
    else:
        return 'raise exc_info[0], exc_info[1], exc_info[2]'

在你的函数里,你可以写这个:

def start_response(status, response_headers, exc_info = None):
    if exc_info:
        try:
            if headers_sent:
                # Re-raise original exception if headers sent.
                exec(what_to_run())
        finally:
            # Avoid dangling circular ref.
            exc_info = None
    elif headers_set:
        raise AssertionError("Headers already set!")

    headers_set[:] = [status, response_headers]
    return write

虽然有点乱,没经过测试,但它应该能工作,至少你明白这个思路。

撰写回答