开发者

catch wrong-arguments exception, in the general case

开发者 https://www.devze.com 2023-04-06 07:10 出处:网络
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, with

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 tr开发者_如何学运维ivial (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?


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.


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.


Ugh unfortunately not really. Your best bet is to introspect the error object that is returned and see if myfunc and the number of arguments is mentioned.

So you'd do something like:

except TypeError, err:
    if err.has_some_property or 'myfunc' in str(err):
        fixit()
    raise


you can do it by doing something like

>>> def f(x,y,z):
    print (f(0))


>>> try:
    f(0)
except TypeError as e:
    print (e.__traceback__.tb_next is None)


True
>>> try:
    f(0,1,2)
except TypeError as e:
    print (e.__traceback__.tb_next is None)


False

but a better way should be to count the number of args of function and comparing with the number of args expected

len(inspect.getargspec(f).args) != len (args)


You can retrieve the traceback and look at its length. Try:

import traceback as tb
import sys

def a():
    1/0

def b():
    a()

def c():
    b()

try:
    a()
except:
    print len(tb.extract_tb(sys.exc_traceback))

try:
    b()
except:
    print len(tb.extract_tb(sys.exc_traceback))

try:
    c()
except:
    print len(tb.extract_tb(sys.exc_traceback))

This prints

2
3
4


Well-written wrappers will preserve the function name, signature, etc, of the functions they wrap; however, if you have to support wrappers that don't, or if you have situations where you want to catch an error in a wrapper (not just the final wrapped function), then there is no general solution that will work.


I know this is an old post, but I stumbled with this question and later with a better answer. This answer depends on a new feature in python 3, Signature objects

With that feature you can write:

sig = inspect.signature(myfunc)
try:
    sig.bind(*args)
except TypeError:
    return fixit()
else:
    f(*args)


Seems to me what you're trying to do is exactly the problem that exceptions are supposed to solve, ie where an exception will be caught somewhere in the call stack, so that there's no need to propagate errors upwards.

Instead, it sounds like you are trying to do error handling the C (non-exception handling) way, where the return value of a function indicates either no error (typically 0) or an error (non 0 value). So, I'd try just writing your function to return a value, and have the caller check for the return value.

0

精彩评论

暂无评论...
验证码 换一张
取 消