views:

196

answers:

2

When you invoke a function with the wrong number of arguments, or with a keyword argument that isn't in its definition, you get a TypeError. I'd like a piece of code to take a callback and invoke it with variable arguments, based on what the callback supports. One way of doing it would be to, for a callback cb, use cb.__code__.cb_argcount and cb.__code__.co_varnames, but I would rather abstract that into something like apply, but that only applies the arguments which "fit".

For example:

 def foo(x,y,z):
   pass

 cleanvoke(foo, 1)         # should call foo(1, None, None)
 cleanvoke(foo, y=2)       # should call foo(None, 2, None)
 cleanvoke(foo, 1,2,3,4,5) # should call foo(1, 2, 3)
                           # etc.

Is there anything like this already in Python, or is it something I should write from scratch?

+7  A: 

Rather than digging down into the details yourself, you can inspect the function's signature -- you probably want inspect.getargspec(cb).

Exactly how you want to use that info, and the args you have, to call the function "properly", is not completely clear to me. Assuming for simplicity that you only care about simple named args, and the values you'd like to pass are in dict d...

args = inspect.getargspec(cb)[0]
cb( **dict((a,d.get(a)) for a in args) )

Maybe you want something fancier, and can elaborate on exactly what?

Alex Martelli
getargspec will definitely make this a lot more "clean" in the way that was worrying me (don't have to look through __magic__ stuff, for example).I intend on filtering named args as you suggested (without the "k" instead of "a" typo ;)), and normal args by length - fill in None if there aren't enough arguments in the call, and trim extra arguments.Maybe I'll make it into a decorator, also :)
Andrey Fedorov
On second thought, you're absolutely right that I didn't make it very clear (in my own head, also) how I want this to behave when the callback has different combinations of defaults, *args, and **kwargs...
Andrey Fedorov
Heh, thanks, fixed the typo. Having thought more about it, I do believe focusing on named args is a good idea - mixing positional and named is always fussy. One key enhancement to my code would be: if argument a has a default value and isn't in d, let it alone rather than forcing it to None as my code does (by the .get).
Alex Martelli
+3  A: 

This maybe?

def fnVariableArgLength(*args, **kwargs):
    """
    - args is a list of non keywords arguments
    - kwargs is a dict of keywords arguments (keyword, arg) pairs
    """
    print args, kwargs


fnVariableArgLength() # () {}
fnVariableArgLength(1, 2, 3) # (1, 2, 3) {}
fnVariableArgLength(foo='bar') # () {'foo': 'bar'}
fnVariableArgLength(1, 2, 3, foo='bar') # (1, 2, 3) {'foo': 'bar'}


Edit Your use cases

def foo(*args,*kw):
    x= kw.get('x',None if len(args) < 1 else args[0])
    y= kw.get('y',None if len(args) < 2 else args[1])
    z= kw.get('z',None if len(args) < 3 else args[2])
    # the rest of foo

foo(1)         # should call foo(1, None, None)
foo(y=2)       # should call foo(None, 2, None)
foo(1,2,3,4,5) # should call foo(1, 2, 3)
NicDumZ
Nah, sorry about not being clear enough - I added some examples to explain what I meant.
Andrey Fedorov