5

I am refactoring a piece of code, and I have run into the following problem. I have a huge parameter list, which now I want to pass as kwargs. The code is like this:

def f(a, b, c, ...):
  print a
  ...

f(a, b, c, ...)

I am refactoring it to:

data = dict(a='aaa', b='bbb', c='ccc', ...)
f(**data)

Which means I have to do:

def f(**kwargs):
  print kwargs['a']
  ...

But this is a pita. I would like to keep:

def f(**kwargs):
  # Do some magic here to make the kwargs directly accessible
  print a
  ...

Is there any straightforward way of making the arguments in the kwargs dict directly accessible, maybe by using some helper class / library?

blueFast
  • 41,341
  • 63
  • 198
  • 344

6 Answers6

7

There are some ways - but you can also wrap your function like this:

def f(**kwargs):
    arg_order = ['a', 'b', 'c', ...]
    args = [kwargs.get(arg, None) for arg in arg_order]

    def _f(a, b, c, ...):
        # The main code of your function

    return _f(*args)

Sample:

def f(**kwargs):
    arg_order = ['a', 'b', 'c']
    args = [kwargs.get(arg, None) for arg in arg_order]

    def _f(a, b, c):
        print a, b, c

    return _f(*args)

data = dict(a='aaa', b='bbb', c='ccc')
f(**data)

Output:

>>> 
aaa bbb ccc
Community
  • 1
  • 1
Inbar Rose
  • 41,843
  • 24
  • 85
  • 131
  • Very smart trick, indeed. As a side question, I have always wondered if the inner function get re-declared each time by the interpreter. Are there downsides to this approach, esp. when the (outer) function is called in a loop? –  Nov 21 '13 at 08:52
  • 1
    This is actually brilliant. – aIKid Nov 21 '13 at 08:53
  • @Tibo I think that is just an example, you can declare the `_f` outside of the wrapper. – aIKid Nov 21 '13 at 08:55
  • This is doing exactly what I asked for. The method used is a bit awkward, but I will probably use it. Thanks! – blueFast Nov 21 '13 at 09:10
  • @alKid: I understand it's an example, but given the elegance of the inner declaration (things are kept organized), it would be even better if we had some kind of guarantee that it will not slow down the interpreter. Although I can optimize the code for loops by taking `_f` out of the wrapper when I need to, I would feel even better if I could have both the elegance of the construct, *and* the execution speed of more traditional approaches. Anyway, I (almost) always favor elegance over (alleged) execution speed. –  Nov 21 '13 at 10:44
  • 1
    Is there a typo in the second line of the first example? I'm guessing `a, b, c` should all be quoted? – Jonathon Reinhart Nov 21 '13 at 22:34
  • @JonathonReinhart Good call - I missed that. Thanks. – Inbar Rose Nov 22 '13 at 08:36
3

Well, you can update locals manually, but the documentation specifically warns against it.

for key, value in kwargs.iteritems(): #Python 2.7 here
    locals()[key] = value

The other option is using exec, which though usually frowned on, is at least guaranteed to work correctly.

for key, value in kwargs.iteritems(): #Python 2.7 here
    exec('{} = value'.format(key)

Though I wouldn't ever admit to anyone that you actually did either of these.

Paul Draper
  • 78,542
  • 46
  • 206
  • 285
  • 1
    I strongly doubt that exec is any better than locals. – Vikram Saran Nov 21 '13 at 08:48
  • 4
    @VikramSaran, see here for why `locals` will not always work: http://stackoverflow.com/questions/8028708/dynamically-set-local-variable-in-python `exec` might not be more elegant, but at least it isn't wrong. – Paul Draper Nov 21 '13 at 08:48
  • Thanks, but `locals` and `exec` are not really my piece of cake. Too low-level. – blueFast Nov 21 '13 at 09:11
1

Inside the function:

for k, v in kwargs.iteritems():
    locals()[k] = v
vahid abdi
  • 9,636
  • 4
  • 29
  • 35
  • 3
    See http://stackoverflow.com/questions/8028708/dynamically-set-local-variable-in-python for why this will not (always) work. – Paul Draper Nov 21 '13 at 08:49
1

I have had to refactor my code in similar cases and I share your dislike of using kwargs['a'], which just feels a bit awkward. Sometimes I use a bunch object instead of a dictionary, which allows you to access fields by attribute access (params.a) instead of a dictionary lookup. It saves you only 3 characters of typing for every time you use a parameter, but I find it much better looking since you do not need quotes around the parameter name. There are various recipes around to implement one, see e.g. these ones.

So instead of using a dict like in your case, you would use it like

In [1]: class Bunch:
   ...:     def __init__(self, **kwds):
   ...:         self.__dict__.update(kwds)

In [2]: params = Bunch(a = 'aaa', b = 'bbb')

In [3]: def f(p):
   ...:     print p.a
   ...:     

In [4]: f(params)
aaa

I know this is not a direct answer to your question, but it is just an alternative for using kwargs.

Bas Swinckels
  • 18,095
  • 3
  • 45
  • 62
  • 1
    Actually, this quite close to what I am looking for. It is easier to type (paste `params.` before all parameters used), and looks nice. The other options (using `locals` and `exec`) seem a receipt for disaster :) Thanks everybody anyway for the interesting ideas. – blueFast Nov 21 '13 at 09:09
  • 1
    And this has the extra advantage that I do not need to type the huge list of keys again (as I have to do in the answer of @InbarRose) – blueFast Nov 21 '13 at 09:18
1

Another possible method (Based on your comment here) is to use an Attribute Dictionary:

class AttributeDict(dict):
    def __getattr__(self, attr):
        return self[attr] if attr in self.keys() else None
    def __setattr__(self, attr, value):
        self[attr] = value 

To be used like this:

def f(**kwargs):
    kwargs = AttributeDict(kwargs)

Sample:

def f(**kwargs):
    kwargs = AttributeDict(kwargs)
    print kwargs.a, kwargs.b, kwargs.c

data = dict(a='aaa', b='bbb', c='ccc')
f(**data)

Output:

>>> 
aaa bbb ccc

Note: You can always name it x if you want a shorter variable name, then access would just be x.a

Community
  • 1
  • 1
Inbar Rose
  • 41,843
  • 24
  • 85
  • 131
  • Interesting. Very similar to the `Bunch` suggestion, right? Actually, Bunch seems to be what you call an `AttributeDict`. – blueFast Nov 21 '13 at 09:26
  • The difference here being how it is constructed, the `Bunch` needs to be created as a Bunch, whereas the `AttributeDict` can be created from any Dictionary. – Inbar Rose Nov 21 '13 at 09:29
  • Whereas an AttributeDict needs to be created as an AttributeDict? I don't see the difference. You can create a Bunch from a dictionary as well with `kwargs = Bunch(**kwargs)`. Alternatively, you could change its constructor to have a slightly different signature. Then there is the trivial variation to create the Bunch/AttributeDict either outside the function or inside the function, as you prefer. – Bas Swinckels Nov 21 '13 at 09:51
0

It seems to me that you're doing redundand job, and easiest solution wouldb leave as it is. Those a, b, c are keyword arguments as well as positional, so you can call your function the way you like:

>>> def f(a, b, c=3):
...     print a, b, c

With all keyword args

>>> f(**{'a':1, 'b':2}) 
1 2 3

With mix of positional and keyword args

>>> f(5, **{'b':4})
5 4 3

And get proper error in case of wrong keyword args

>>> f(**{'d':4, 'a':1, 'b':2}) 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() got an unexpected keyword argument 'd'
alko
  • 46,136
  • 12
  • 94
  • 102