Python 更改异常可打印输出,例如过载 __builtins__

Python change Exception printable output, eg overload __builtins__

我正在寻找一种方法来将异常的可打印输出更改为愚蠢的消息,以便了解有关 python 内部结构的更多信息(并与朋友打交道;),但到目前为止没有成功。

考虑以下代码

try:
   x # is not defined
except NameError as exc:
   print(exc)

代码应输出name 'x' is not defined

我想要将输出更改为 the name 'x' you suggested is not yet defined, my lord. Improve your coding skills

到目前为止,我了解到您无法更改 __builtins__,因为它们是作为 C 代码“内置”的,除非:

  1. 您使用 forbiddenfruit.curse 方法添加/更改任何对象的属性
  2. 您手动覆盖对象的字典

我已经尝试了两种解决方案,但都没有成功:

禁果解决方案:

from forbiddenfruit import curse

curse(BaseException, 'repr', lambda self: print("Test message for repr"))
curse(BaseException, 'str', lambda self: print("Test message for str"))

try:
    x
except NameError as exc:
    print(exc.str()) # Works, shows test message
    print(exc.repr()) # Works, shows test message
    print(repr(exc)) # Does not work, shows real message
    print(str(exc)) # Does not work, shows real message
    print(exc) # Does not work, shows real message

字典覆盖解决方案:

import gc

underlying_dict = gc.get_referents(BaseException.__dict__)[0]
underlying_dict["__repr__"] = lambda self: print("test message for repr")
underlying_dict["__str__"] = lambda self: print("test message for str")
underlying_dict["args"] = 'I am an argument list'

try:
    x
except NameError as exc:
    print(exc.__str__()) # Works, shows test message
    print(exc.__repr__()) # Works, shows test message
    print(repr(exc)) # Does not work, shows real message
    print(str(exc)) # Does not work, shows real message
    print(exc) # Does not work, shows real message

AFAIK,使用 print(exc) 应该依赖于 __repr____str__,但似乎 print 函数使用了其他东西,即使在通过 print(dir(BaseException)) 读取 BaseException 的所有属性。 谁能告诉我 print 在这种情况下使用的是什么?

[编辑]

要添加更多上下文:

我试图解决的问题最初是和一位程序员朋友开的玩笑,但现在对我来说是一个挑战,需要我了解更多 python 的内部结构。

我没有试图解决真正的业务问题,我只是想更深入地了解 Python. 中的事物 我很困惑 print(exc) 实际上不会使用 BaseException.__repr____str__

[/编辑]

像这样的错误 hard-coded 进入解释器(在 CPython 的情况下,无论如何,这很可能是您正在使用的)。您将无法更改从 Python 内部打印的消息。

当 CPython 解释器尝试查找名称时执行的 C 源代码可以在这里找到:https://github.com/python/cpython/blob/master/Python/ceval.c#L2602. If you would want to change the error message printed when a name lookup fails, you would need to change this line 在同一文件中:

#define NAME_ERROR_MSG \
    "name '%.200s' is not defined"

编译修改后的源代码会产生一个 Python 解释器,它在遇到未定义的名称时打印您的自定义错误消息。

简介

对于为什么你甚至想做你想做的事,我会采用更批判的方法。

Python 为您提供了处理特定异常的能力。这意味着如果您遇到业务问题,您将使用特定的异常 class 并为该特定案例提供自定义消息。现在,记住这一段,让我们继续,我稍后会提到这个。


TL;DR

现在,让我们开始吧top-down:

except Exception 捕获所有类型的错误通常不是一个好主意,如果你想捕获比方说变量名错误的话。你会使用 except NameError 来代替。您实际上不需要添加太多内容,这就是为什么它有一条完美描述问题的默认消息。因此,假设您会按给定的方式使用它。这些被称为具体例外。

现在,根据您的具体情况,请注意别名 as exc。通过使用别名,您可以访问传递给异常对象的参数,包括默认消息。

try:
   x # is not defined
except NameError as exc:
   print(exc.args)

运行 那个代码(我把它放在 app.py 中)你会看到:

$ python app.py
("name 'x' is not defined",)

这些 args 作为系列(列表,或在本例中为元组的不可变列表)传递给异常。

这导致了可以轻松地将参数传递给异常构造函数的想法 (__init__)。在您的情况下,"name 'x' is not defined" 作为参数传递。

您可以利用它来解决您的问题,只需提供一条自定义消息,例如:

try:
   x # is not defined
except NameError as exc:
   your_custom_message = "the name 'x' you suggested is not yet defined, my lord. Improve your coding skills"
   # Now, you can handle it based on your requirement:
   #  print(your_custom_message)
   #  print(NameError(your_custom_message))
   #  raise NameError(your_custom_message)
   #  raise NameError(your_custom_message) from exc

输出现在就是您想要实现的。

$ python app.py
the name 'x' you suggested is not yet defined, my lord. Improve your coding skills

还记得第一段我说我稍后会参考吗?我提到为特定案例 提供自定义消息 。如果您在想要处理与产品相关的特定变量的名称错误时构建自己的库,则假设您的用户将使用您的代码,这可能会引发 NameError 异常。他们很可能会用 except Exception as excexcept NameError as exc 捕捉到它。当他们这样做时 print(exc),他们现在就会看到您的消息。


总结

我希望这对您有意义,只需提供自定义消息并将其作为参数传递给 NameError 或直接打印即可。 IMO,最好把它和你为什么要使用你所用的东西一起学习。

我将解释您描述的行为:

  • exc.__repr__()

这只会调用您的 lambda 函数和 return 预期的字符串。顺便说一句,你应该 return 字符串,而不是在你的 lambda 函数中打印它。

  • print(repr(exc))

现在,这在 CPython 中走另一条路,您可以在 GDB 会话中看到它,它是这样的:

Python/bltinmodule.c:builtin_repr 将调用 Objects/object.c:PyObject_Repr - 此函数获取 PyObject *v 作为唯一参数,它将用于获取和调用实现 built-in 函数的函数repr()BaseException_repr 在这种情况下。此函数将根据来自 args 结构字段的值格式化错误消息:

(gdb) p ((PyBaseExceptionObject *) self)->args 
8 = ("name 'x' is not defined",)

args 值是根据同一文件中设置的 NAME_ERROR_MSG 宏在 Python/ceval.c:format_exc_check_arg 中设置的。

更新:11 月 8 日星期日 20:19:26 UTC 2020

test.py:

import sys
import dis


def main():
    try:
        x
    except NameError as exc:
        tb = sys.exc_info()[2]
        frame, i = tb.tb_frame, tb.tb_lasti
        code = frame.f_code
        arg = code.co_code[i + 1]
        name = code.co_names[arg]
        print(name)


if __name__ == '__main__':
    main()

测试:

# python test.py
x

注:

我还建议观看 PyCon 2016 的 this 视频。