问题描述
我正在寻找一种将Exception的可打印输出更改为愚蠢消息的方法,以便进一步了解python内部信息(并与朋友;混乱),
考虑以下代码
try:
x # is not defined
except NameError as exc:
print(exc)
我希望将更改输出到the name 'x' you suggested is not yet defined,my lord. Improve your coding skills
。
到目前为止,我了解到您无法更改__builtins__
,因为它们已作为C代码“加入”,除非:
我尝试过两种解决方案,但都没有成功:
禁果解决方案:
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
函数使用其他东西,即使阅读全部,我也找不到BaseException
到print(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 exc
或except 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视频。