Incorrect number of arguments

Andrew Dalke dalke at dalkescientific.com
Thu Jun 9 03:51:32 EDT 2005


Steven D'Aprano wrote:
> *eureka moment*
> 
> I can use introspection on the function directly to see 
> how many arguments it accepts, instead of actually 
> calling the function and trapping the exception.

For funsies, the function 'can_call' below takes a function 'f'
and returns a new function 'g'.  Calling 'g' with a set of
arguments returns True if 'f' would take the arguments,
otherwise it returns False.  See the test case for an
example of use.


import new

def noop():
    pass


def can_call(func):
    # Make a new function with the same signature

    # code(argcount, nlocals, stacksize, flags, codestring, constants, names,
    #      varnames, filename, name, firstlineno, lnotab[, freevars[, cellvars]])
    code = func.func_code
    new_code = new.code(code.co_argcount,
                        code.co_nlocals,
                        
                        noop.func_code.co_stacksize,
                        
                        code.co_flags,
                        
                        noop.func_code.co_code,  # don't do anything
                        
                        code.co_consts,
                        code.co_names,
                        code.co_varnames,
                        code.co_filename,
                        "can_call_" + code.co_name,
                        code.co_firstlineno,
                        
                        noop.func_code.co_lnotab, # for line number info
                        
                        code.co_freevars,
                        # Do I need to set cellvars?  Don't think so.
                        )

    # function(code, globals[, name[, argdefs[, closure]]])
    new_func = new.function(new_code, func.func_globals,
                            "can_call_" + func.func_name,
                            func.func_defaults)

    # Uses a static scope    
    def can_call_func(*args, **kwargs):
        try:
            new_func(*args, **kwargs)
        except TypeError, err:
            return False
        return True
    try:
        can_call_func.__name__ = "can_call_" + func.__name__
    except TypeError:
        # Can't change the name in Python 2.3 or earlier
        pass
    return can_call_func


#### test

def spam(x, y, z=4):
    raise AssertionError("Don't call me!")


can_spam = can_call(spam)

for (args, kwargs) in (
    ((1,2), {}),
    ((1,), {}),
    ((1,), {"x": 2}),
    ((), {"x": 1, "y": 2}),
    ((), {"x": 1, "z": 2}),
    ((1,2,3), {}),
    ((1,2,3), {"x": 3}),
    ):
    can_spam_result = can_spam(*args, **kwargs)
    try:
        spam(*args, **kwargs)
    except AssertionError:
        could_spam = True
    except TypeError:
        could_spam = False

    if can_spam_result == could_spam:
        continue

    print "Failure:", repr(args), repr(kwargs)
    print "Could I call spam()?", could_spam
    print "Did I think I could?", can_spam_result
    print

print "Done."


> Still a good question though. Why is it TypeError?

My guess - in most languages with types, functions are
typed not only on "is callable" but on the parameter
signature.  For example, in C


dalke% awk '{printf("%3d %s\n", NR, $0)}' tmp.c
  1 
  2 int f(int x, int y) {
  3 }
  4 
  5 int g(int x) {
  6 }
  7 
  8 main() {
  9   int (*func_ptr)(int, int);
 10   func_ptr = f;
 11   func_ptr = g;
 12 }
% cc tmp.c
tmp.c: In function `main':
tmp.c:11: warning: assignment from incompatible pointer type
% 

'Course the next question might be "then how about an
ArgumentError which is a subclasss of TypeError?"

				Andrew
				dalke at dalkescientific.com




More information about the Python-list mailing list