15

Is there a more succint way to write this?

f(a=a, b=b, c=c, d=d, e=e)

Background: I have a function with too many arguments

f(a, b, c, d, e):
    pass

I my program I have local variables that are named exactly same as the function parameters.

a, b, c, d, e = range(5)

I would like to call the function with keyword arguments. Since the variables are named the same, this is how the call would look.

g = f(a=a, b=b, c=c, d=d, e=e) # this can get very long

Of course, I can pass the aruguments using position instead of keywords like this

g = f(a, b, c, d, e) 

But a, b, c, d, e are just the names of variables in this example and it is easy to see the correct order. However unfortunately the variables in my program are named more complicatedly and there is no easily discernible natural order. So I really like to pass them by keyword to avoid any mistakes.

Praveen Gollakota
  • 37,112
  • 11
  • 62
  • 61

3 Answers3

7

You could do something like the following:

a, b, c, d, e = range(5)
arg_dict = lambda l: dict((k, globals()[k]) for k in l.split(', '))

arg_dict('a, b, c, d, e') => {'a': 0, 'c': 2, 'b': 1, 'e': 4, 'd': 3}, so you can call your function like this:

f(**arg_dict('a, b, c, d, e'))

This gives you the ability to specify exactly which variables you want to use. An alternative method for this that does not use globals() would be to use eval(), but it could make the use of the lambda potentially unsafe.

arg_dict = lambda l: dict(zip(l.split(', '), eval(l)))

If you would prefer to pass locals() in as an argument instead of using globals() in the lambda you can use the following:

arg_dict = lambda l, d=locals(): dict((k, d[k]) for k in l.split(', '))
f(**arg_dict('a, b, c, d, e'))

Thanks to senderle for the locals() suggestions.

Community
  • 1
  • 1
Andrew Clark
  • 202,379
  • 35
  • 273
  • 306
  • The `lambda` has its own scope, so the local variables from its containing scope are not in `locals()` if it is used inside the lambda (but they will be in `globals()`). – Andrew Clark May 02 '11 at 19:24
  • Wunderbar! This is exactly what I was looking for. Thanks! I agree with @senderle that I could use `locals()` instead of `globals()` though. – Praveen Gollakota May 02 '11 at 19:24
  • You can try it, but `locals()` won't work unless you pass it into the lambda as an argument. You can verify this with `(lambda: locals())()` and `(lambda: globals())()`. – Andrew Clark May 02 '11 at 19:27
  • @Andrew, ah, of course -- that's quite correct. And indeed, my inclination would be to do just that -- pass it in as a argument to lambda. – senderle May 02 '11 at 19:34
  • @Andrew: Good point. I forgot that lambda has it's own scope. `globals()` it is then. Thanks for the explanation. – Praveen Gollakota May 02 '11 at 19:44
  • @Andrew, could you pass `locals` in as a default arg? As in `arg_dict = lambda l, d=locals(): dict((k, d[k]) for k in l.split(', '))`? – senderle May 02 '11 at 19:49
4

locals() gives your local variables, so you could do

def somewhere():
  x = 3 # its a local
  f(**locals()) # same as f(x=3)

but you can surely see how very fragile this is.

Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
  • I was thinking about locals too. But it did not seem like the 'elegant' way to solve this. If all fails, may be I should stick with brute force! :) – Praveen Gollakota May 02 '11 at 19:05
1

Why can't you use **kw here?

def foo(**kw):
    for k,v in kw.items():
       print k,v


foo(a=2)
foo(a=3, b=4)
foo(nonsene=True, blather=False)
  • I agree that it would be the natural choice. However, foo() is defined in an external module and I don't have control over how the function is defined. – Praveen Gollakota May 02 '11 at 18:43
  • One alternative is to define bar(), as a wrapper for foo() and passes the arguments, but I was wondering if there was an easier way. – Praveen Gollakota May 02 '11 at 18:44