8

i'm new to python and need some help...

I'm implementing a generic search function that accepts an argument "fringe", which can be a data structure of many types.

in the search method I have the line:

 fringe.push(item, priority)

the problem is that the push method in different data structures takes different number of arguments(some require priority and some dont). is there an ellegant way to get pass that and make the "push" method take only the number of args it requires out of the argument list sent?

Thanks!

user1049052
  • 123
  • 1
  • 4
  • 10

4 Answers4

16

The method to get different number of arguments and still being able of selecting the right one is the use of *args and **keyword_args parameters. From Mark Lutz's Learning Python book:

* and **, are designed to support functions that take any number of arguments. Both can appear in either the function definition or a function call, and they have related purposes in the two locations.

* and ** in function definition

If you define a function:

def f1(param1, *argparams, **kwparams): 
    print 'fixed_params -> ', param1
    print 'argparams  --> ', argparams
    print 'kwparams   ---->,', kwparams

you can call it this way:

f1('a', 'b', 'c', 'd', kw1='keyw1', kw2='keyw2')

Then you get:

fixed_params ->  a
argparams  -->  ('b', 'c', 'd')
kwparams   ---->, {'kw1': 'keyw1', 'kw2': 'keyw2'}

So that you can send/receive any number of parameters and keywords. One typical idiom to recover keyword args is as follows:

def f1(param1, **kwparams):
    my_kw1 = kwparams['kw1']
    ---- operate  with my_kw1 ------

In this way your function can be called with any number of params and it uses those it needs.
This type or arguments are frecuently used in some GUI code like wxPython class definition and subclassing as well as for function currying, decorators, etc

* and ** in function call

* and ** params in a function call are unpacked when taken by the function:

def func(a, b, c, d):
    print(a, b, c, d)

args = (2, 3)
kwargs = {'d': 4}
func(1, *args, **kwargs)

### returns ---> 1 2 3 4 

Great!

joaquin
  • 82,968
  • 29
  • 138
  • 152
  • How does this address the question which I think was about calling an existing function that might accept one or two arguments but the caller doesn't know which? – Duncan Nov 16 '11 at 09:40
  • @Duncan If you dont know which arguments to send, then either you introspect the method, you play try/failure with exceptions or you send all the possible known parameters and let the different object methods to get the ones they need. This last approach is straighforward and fairly common in many situations. For the OP example you can solve the problem simply with a keyword default argument. As I was not sure whether the OP was talking about *one or two* arguments or about a more general *one or more* arguments I gave him a general view or *args and **kargs. Hope he find it useful – joaquin Nov 16 '11 at 10:26
  • This is an excellent answer, if you can change the definition of the function you are calling. If not, something like @Duncan's answer seems to be necessary. Library developers can avoid this issue by making user-defined callback methods take *args and **kwargs. That will allow you to add parameters to the callback indefinitely in the future without breaking anything or needing to inspect the callbacks. – jtpereyda Jul 07 '15 at 17:49
2

In theory you could use inspect.getargspec(fringe) to find out what arguments the method takes. That will tell you the number of arguments you could pass, but it's very messy:

argspec = inspect.getargspec(fringe.push)
if len(argspec.args) >= 3 or argspec.varargs or argspec.keywords:
    fringe.push(item, priority)
else:
    fringe.push(item)

Much simpler just to go for it and ask forgiveness if necessary:

try:
    fringe.push(item, priority)
except TypeError:
    fringe.push(item)

Better still of course to make sure that the push() methods all have a consistent argument spec, but if you can't do that then use the try...except.

Duncan
  • 92,073
  • 11
  • 122
  • 156
  • The situation where this approach is necessary for me is when dealing with a library where I want to keep old callback signatures backwards-compatible. In this case, I'm a little apprehensive to catch a TypeError, since I won't know if it's from the callback signature or from some Exception in the user's callback code. – jtpereyda Jul 07 '15 at 17:03
1

try below code snippets,

def push(item, priority=None):
    print item,priority


args = (1,)
push(*args)

args = (1,2)
push(*args)
Yajushi
  • 1,175
  • 2
  • 9
  • 24
0

Can't you just use a default argument value?

>>> def foo(a, b = 10):
...   print a, b
... 
>>> foo(1000)
1000 10
>>> foo(1000, 1000)
1000 1000
>>>

If the argument b is not provided, it defaults to 10.

Blender
  • 289,723
  • 53
  • 439
  • 496
  • if i get you correctly - this requires changing the push method in each data structure that the fringe can be... since i want the search function to be generic, i want it to work with existing data structures. the manipulation needs to be in the search method itself. is there a good way to do that? – user1049052 Nov 16 '11 at 06:53
  • You'll have to see which datatypes require the parameter and which don't. You could (not recommended) use a `try, except` block and try to run `.push` without a parameter. If it breaks, try the parameter. I doubt it'll be fast, though. – Blender Nov 16 '11 at 06:57