Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

catch wrong-arguments exception, in the general case

I want to catch an exception, but only if it comes from the very next level of logic.

The intent is to handle errors caused by the act of calling the function with the wrong number of arguments, without masking errors generated by the function implementation.

How can I implement the wrong_arguments function below?

Example:

try:
    return myfunc(*args)
except TypeError, error:
    #possibly wrong number of arguments
    #we know how to proceed if the error occurred when calling myfunc(), 
    #but we shouldn't interfere with errors in the implementation of myfunc
    if wrong_arguments(error, myfunc):
        return fixit()
    else:
        raise

Addendum:

There are several solutions that work nicely in the simple case, but none of the current answers will work in the real-world case of decorated functions.

Consider that these are possible values of myfunc above:

def decorator(func):
    "The most trivial (and common) decorator"
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

def myfunc1(a, b, c='ok'):
    return (a, b, c)

myfunc2 = decorator(myfunc1)
myfunc3 = decorator(myfunc2)

Even the conservative look-before-you-leap method (inspecting the function argument spec) fails here, since most decorators will have an argspec of *args, **kwargs regardless of the decorated function. Exception inspection also seems unreliable, since myfunc.__name__ will be simply "wrapper" for most decorators, regardless of the core function's name.

Is there any good solution if the function may or may not have decorators?

like image 222
bukzor Avatar asked Sep 21 '11 18:09

bukzor


2 Answers

You can do:

    try:
        myfunc()
    except IndexError:
        trace = sys.exc_info()[2]
        if trace.tb_next.tb_next is None:
            pass
        else:
            raise

Although it is kinda ugly and would seem to violate encapsulation.

Stylistically, wanting to catch having passed too many arguments seem strange. I suspect that a more general rethink of what you are doing may resolve the problem. But without more details I can't be sure.

EDIT

Possible approach: check if function you are calling has the arguments *args,**kwargs. If it does, assume its a decorator and adjust the code above to check if the exception was one further layer in. If not, check as above.

Still, I think you need to rethink your solution.

like image 143
Winston Ewert Avatar answered Oct 30 '22 18:10

Winston Ewert


I am not a fan of doing magic this way. I suspect you have an underlying design problem rather.

--original answer and code which was too unspecific to the problem removed--

Edit after understanding specific problem:

from inspect import getargspec

def can_call_effectively(f, args):
    (fargs, varargs, _kw, df) = getattr(myfunc, 'effective_argspec', \
        getargspec(myfunc))
    fargslen = len(fargs)
    argslen = len(args)
    minargslen = fargslen - len(df)
    return (varargs and argslen >= minargslen) or minargslen <= argslen <= fargslen

if can_call_effectively(myfunc, args)
    myfunc(*args)
else:
    fixit()

All your decorators, or at least those you want to be transparent in regard to calling via the above code, need to set 'effective_argspec' on the returned callable. Very explicit, no magic. To achieve this, you could decorate your decorators with the appropriate code...

Edit: more code, the decorator for transparent decorators.

def transparent_decorator(decorator):
    def wrapper(f):
        wrapped = decorator(f)
        wrapped.__doc__ = f.__doc__
        wrapped.effective_argspec = getattr(f, 'effective_argspec', getargspec(f))
        return wrapped
    return wrapper

Use this on your decorator:

@transparent_decorator
def decorator(func):
"The most trivial (and common) decorator"
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper  # line missing in example above

Now if you create myfunc1 - myfunc3 as above, they work exactly as expected.

like image 4
Jürgen Strobel Avatar answered Oct 30 '22 19:10

Jürgen Strobel