Python更改Exception可打印的输出,例如,重载__builtins __

问题描述

我正在寻找一种将Exception的可打印输出更改为愚蠢消息的方法,以便进一步了解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 real message

使用print(exc)的AFAIK应该依赖__repr____str__,但是似乎print函数使用其他东西,即使阅读全部,我也找不到BaseExceptionprint(dir(BaseException))属性。 有人可以给我介绍print在这种情况下使用什么吗?

[编辑]

添加更多上下文:

我要解决的问题始于开玩笑,与一个程序员朋友打成一片,但现在对我来说,要了解更多有关python内部结构的挑战成为了挑战。

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

[/ EDIT]

解决方法

这样的错误被硬编码到解释器中(对于CPython,无论如何,这很可能是您所使用的)。您将无法更改从Python本身打印的消息。

在CPython解释器尝试查找名称时执行的C源代码可以在这里找到:https://github.com/python/cpython/blob/master/Python/ceval.c#L2602。如果要更改在名称查找失败时打印的错误消息,则需要在同一文件中更改this line

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

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

,

简介

对于为什么您甚至想做自己想做的事情,我会采用更关键的方法。

Python为您提供了处理特定异常的能力。这意味着,如果您遇到业务问题,则可以使用特定的异常类并为该特定情况提供自定义消息。现在,请记住这一段,让我们继续,稍后再参考。


TL; DR

现在,让我们自上而下:

使用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函数并返回预期的字符串。顺便说一句,您应该返回字符串,而不是在lambada函数中将其打印出来。

  • print(repr(exc))

现在,这在CPython中走了一条不同的路线,您可以在GDB会话中看到这一点,就像这样:

Python/bltinmodule.c:builtin_repr将调用Objects/object.c:PyObject_Repr-此函数将PyObject *v作为唯一参数,它将用于获取和调用实现内置函数{{1}的函数},repr()。此函数将根据BaseException_repr结构字段中的值来格式化错误消息:

args

根据同一文件中设置的(gdb) p ((PyBaseExceptionObject *) self)->args $188 = ("name 'x' is not defined",) 宏,在args中设置了Python/ceval.c:format_exc_check_arg值。

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

test.py:

NAME_ERROR_MSG

测试:

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

注意:

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