【问题标题】:How to write exception reraising code that's compatible with both Python 2 and Python 3?如何编写与 Python 2 和 Python 3 兼容的异常重新引发代码?
【发布时间】:2013-01-24 14:33:24
【问题描述】:

我正在尝试使我的 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 })

【问题讨论】:

  • 这对我来说没有多大意义.. 你能把你的 start_response 函数放在一些上下文中吗,也许展示一个你会调用它的例子?
  • 经典的try/except有什么问题?
  • @poke 和 @inbar rose:start_response 是 WSGI 规范的一部分。见 PEP-333。 WSGI 应用程序在想要开始响应时调用 start_response。如果给出了exc_info,那么这是 WSGI 应用程序代码遇到异常的信号,WSGI 服务器应该对它做一些事情,比如打印一个错误。就我而言,如果标头已经发出,我想提出错误。见python.org/dev/peps/pep-0333/#the-start-response-callable
  • 快速澄清一下,您说 Python 3 语法在 python 2 中产生了一个解析错误——在我看来,它应该是相反的。 python 2 语法在 python 3 中生成解析错误...

标签: python python-3.x python-2.x


【解决方案1】:

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

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

见:https://six.readthedocs.io/index.html#six.reraise

【讨论】:

【解决方案2】:

你可以做一些有创意的事情。

在代码的开头进行检查 - 你的构造函数或其他什么,检查你使用的 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

有点乱,未经测试,但它应该工作,至少你理解这个想法。

【讨论】:

  • 有一个更简单的方法:PY3K = sys.version_info >= (3,)(被six等使用)。
  • 检查 Python 不是问题。问题是 Python 3 兼容的代码会在 Python 2 上生成 解析错误,反之亦然。我不能像往常一样将两个版本都放在 if-else 或 try-except 块中。
  • @Hongli -- 我很确定eval 不起作用,因为raise 是一个语句,而不是一个表达式。如果您将其更改为exec,它可能会起作用...
  • @InbarRose -- 但是当eval 需要表达式 时,在语句中使用eval 仍然存在问题
  • 不要这样做。正如其他答案所暗示的那样,请改用six
猜你喜欢
  • 1970-01-01
  • 2018-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-23
  • 1970-01-01
  • 2015-08-06
相关资源
最近更新 更多