Handling same type exceptions separately in Python
Question: Say I have the following four variables:

    
    
    >>> f1 = (print, 1, 2 ,3 ,4)
    >>> f2 = (exit, 1, 2 ,3 ,4)
    >>> f3 = (1, 2, 3, 4)
    >>> f4 = 4
    

In a hypothetical program, I expect each of those variables to hold a tuple,
whose first item should be the name of a function, and whose subsequent items
should be said function's parameters, in order.

I could call a function stored in this way like so:

    
    
    >>> f1[0](*f1[1:])
    1 2 3 4
    

However, most of these variables are not in the excepted format, and I would
like to be able to encapsulate the calling of them inside `try`/`except`
blocks in order to handle those situations.

Now, even though the function calls of `f2`, `f3` and `f4` break for radically
different reason, they all throw the same kind of exception, a `TypeError`:

    
    
    >>> f2[0](*f2[1:])
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: __call__() takes from 1 to 2 positional arguments but 5 were given
    >>> f3[0](*f3[1:])
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: 'int' object is not callable
    >>> f4[0](*f4[1:])
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: 'int' object is not subscriptable
    

So that doing a generic:

    
    
    try:
        f[0](*f[1:])
    except TypeError:
        # handle exception
    

Would not provide me with enough information to handle each exception
accordingly.

What would be the correct way to differentiate between distinct exceptions of
the same type in Python?


Answer: So, you need to use a different method of calling than `function(*tuple)`.
Here is an example of a function `fun_apply` that calls a particular function
with positional and keyword arguments. It adds an explicit check to make sure
that `args` is iterable and `kwargs` inherits from `collection.Mapping`.

    
    
    from collections import Mapping
    
    def fun_apply(f, args=None, kwargs=None):                                                                                
        if args is None:
            args = []
        if kwargs is None:
            kwargs = {}
    
        try:
            args = iter(args)
        except TypeError:
            # handle args being non-iterable
            pass
    
        if isinstance(kwargs, collections.Mapping):
            pass
        else:
            # handle kwargs being a non-mapping
            pass
    
        if hasattr(f, '__call__'):
            pass
        else:
            # handle f being non-callable
            pass
    
        try:
            f(*args, **kwargs)
        except TypeError as e:
            raise TypeError(e)
    

The other option is to process the `message` string explicitly, but this could
have unintended consequences and the error messages themselves might differ
between versions of Python.

