简体   繁体   中英

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. ie:

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?

For number two:

>>> def foo(bar):
...     '''Adds "1" to bar and prints output'''
...     return 1 + bar
...
>>> print foo.__doc__
Adds "1" to bar and prints output

For number three, you may want to use the raise keyword to raise an error. You could probably use this with your first solution, but I've never used the traceback module so I can't help you there, sorry.

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 get_args(func):
    """Get function arguments, varargs, kwargs and defaults.

    This function will be called whenever a exception is raised."""
    args, varargs, kwargs, defs = inspect.getargspec(func)

    if defs is not None:
        # If there are defaults, include them in the main argument list
        for i in range(-len(defs), 0):
            args[i] += ' = {}'.format(defs[i])

    if varargs is not None:
        # If the function accept extra arguments, include them at the end of the list.
        args.append('*' + varargs)

    if kwargs is not None:
        # If the function accept extra keyworded arguments, include them at the end of the list.
        args.append('**' + kwargs)

    return args  # args contain information about all the function arguments.


def value_handler(exc, value, tb):
    """Changes the default message to include additional information.

    This handler will be called whenever an exception happens."""
    args = list(value)  # Value typically contains the error message.

    if exc == TypeError and '()' in str(value):
        # I want to change only TypeError from function calls.
        func_name = str(value).split('()')[0]  # Obtain the function name from the error message.
        func = globals()[func_name]  # Get function object from globals
        func_doc = func.__doc__      # Function docstring
        func_args = get_args(func)   # Get function arguments
        if func_doc is not None:     # Add docstring to the message
            args[0] += '\nDoc: \t{}'.format(func_doc)
        args[0] += '\nArgs: \t' + '\n\t'.join(func_args)  # Finally, add all the arguments to the message.

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

def custom_exc(shell, exc, value, tb, tb_offset=None):
    """Add aditional information and call original excepthook

    This version works with iPython"""
    value = value_handler(exc, value, tb)  # Changes the error message
    shell.showtraceback((exc, value, tb), tb_offset=tb_offset)  # Raise the error with the new message (keeping traceback and other information).

def custom_python_exc(exc, value, tb):
    """Add aditional information and call original excepthook

    This version works with regular python"""
    value = value_handler(exc, value, tb)  # Changes the error message
    sys.__excepthook__(exc, value, tb)     # Raise the error with the new message (keeping traceback and other information).

try:
    __IPYTHON__  # Check if we're running iPython
except NameError:
    # Not iPython enviroment, override excepthook
    sys.excepthook = custom_python_exc
else:
    # iPython enviroment, need to set custom excepthook
    get_ipython().set_custom_exc((Exception,), custom_exc)


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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM