pekapa pekapa - 6 months ago 8
Python Question

Python TypeError to give more information about arguments

I'm trying to implement a system to help the user when calling functions/methods.

I know the user can just

help(function)
to get some kind of a documentation provided by me but I wanted to reimplement the
TypeError
do it would also print that documentation if available.




For example:

Suppose I have:

def foo(bar):
''' Adds 1 to 'bar' and prints output '''
print 1+bar


And the user decide to call
foo()
(with no arguments)

It will raise a TypeError like this:

---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-9-624891b0d01a> in <module>()
----> 1 foo()

TypeError: foo() takes exactly 1 argument (0 given)


I would like it to also print the information from the
help(foo)
as well. i.e.:

foo(bar)
Adds 1 to 'bar' and prints output


Any ideas on how to do that? I realise I need


  1. to detect the function that raised the TypeError

  2. get the help text for that function

  3. add it to the raised TypeError.



for 1) this seems to work:

import sys, traceback
# Get latest traceback information
tb = sys.exc_info()[-1]
stk = traceback.extract_tb(tb, 1)
# Extract called function and remove '()' - This actually limits functionality as the user might had inputed some extra arguments for example
fname = stk[0][-1]
fname = str(fname).split('()')[0]


for 2) and 3) and have no ideas on how to proceed... =/




Very much appreciated!




Edit for 3) I'm trying to override the default behaviour of TypeError, so far with no success.
I created a new MyError class just to test it and made:

import exceptions
exception.TypeError = MyError


but whenever the TypeError is raised, the original version comes up and not MyError




Edit 2 Ok, found out that I actually need to override the sys.excepthook method.

As a test, I created:

import sys
def handler(exc, value, tb):
print 'Oops'

sys.excepthook = handler


However, whenever a error occurs it still brings the original error and not the 'Oops' message. Also,
sys.excepthook
still returns the original message:

<bound method TerminalInteractiveShell.excepthook of <IPython.terminal.interactiveshell.TerminalInteractiveShell object at 0x10f4320d0>>


I also tried overriding the IPython.terminal.interactiveshell.TerminalInteractiveShell.excepthook with no success.

Any ideas on how to keep going?

Answer

Ok, I finally got it!

This answer is valid for both python and ipython! (I only tested with python 2.7, minor changes might be needed for python 3)

import sys
import traceback
import inspect


def value_handler(exc, value, tb):
    '''Changes the default message to include additional information.'''
    args = list(value)
    if exc == TypeError and '(' in str(value):
        # I want to change only TypeError from function calls.
        func_name = str(value).split('(')[0]
        func = globals()[func_name]
        func_doc = func.func_doc
        func_args = inspect.getargspec(func)[0]
        if func_doc is not None:
            args[0] += '\nDoc: \t{}'.format(func_doc)
        args[0] += '\nArgs: \t' + '\n\t'.join(func_args)

    # Apply changes to the original error
    value.args = args
    return value


def custom_ipython_exc(shell, exc, value, tb, tb_offset=None):
    '''Add aditional information and call original excepthook'''
    value = value_handler(exc, value, tb)
    shell.showtraceback((exc, value, tb), tb_offset=tb_offset)


def custom_python_exc(exc, value, tb):
    '''Add aditional information and call original excepthook'''
    value = value_handler(exc, value, tb)
    sys.__excepthook__(exc, value, tb)


try:
    __IPYTHON__
except NameError:
    sys.excepthook = custom_python_exc
else:
    get_ipython().set_custom_exc((Exception,), custom_exc)


With this, one should be able to add more information to any Error being raised.