I am searching for a way to change the printable output of an Exception to a silly message in order to learn more about python internals (and mess with a friend ;), so far without success.
Consider the following code
try: x # is not defined except NameError as exc: print(exc)
The code shall output name 'x' is not defined
I would like the change that output to the name 'x' you suggested is not yet defined, my lord. Improve your coding skills
.
So far, I understood that you can’t change __builtins__
because they’re “baked in” as C code, unless:
- You use forbiddenfruit.curse method which adds / changes properties of any object
- You manually override the dictionnaries of an object
I’ve tried both solutions, but without success:
forbiddenfruit solution:
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
Dictionnary overriding solution:
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, using print(exc)
should rely on either __repr__
or __str__
, but it seems like the print
function uses something else, which I cannot find even when reading all properties of BaseException
via print(dir(BaseException))
.
Could anyone give me an insight of what print
uses in this case please ?
[EDIT]
To add a bit more context:
The problem I’m trying to solve began as a joke to mess with a programmer friend, but now became a challenge for me to understand more of python’s internals.
There’s no real business problem I’m trying to solve, I just want to get deeper understanding of things in Python.
I’m quite puzzled that print(exc)
won’t make use of BaseException.__repr__
or __str__
actually.
[/EDIT]
Advertisement
Answer
I’ll just explain the behaviour you described:
exc.__repr__()
This will just call your lambda function and return the expected string. Btw you should return the string, not print it in your lambda functions.
print(repr(exc))
Now, this is going a different route in CPython
and you can see this in a GDB session, it’s something like this:
Python/bltinmodule.c:builtin_repr
will call Objects/object.c:PyObject_Repr
– this function gets the PyObject *v
as the only parameter that it will use to get and call a function that implements the built-in function repr()
, BaseException_repr
in this case. This function will format the error message based on a value from args
structure field:
(gdb) p ((PyBaseExceptionObject *) self)->args $188 = ("name 'x' is not defined",)
The args
value is set in Python/ceval.c:format_exc_check_arg
based on a NAME_ERROR_MSG
macro set in the same file.
Update: Sun 8 Nov 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()
Test:
# python test.py x
Note:
I would also recommend to watch this video from PyCon 2016.