9

This is something that's bugged me for awhile now:

def test (*args, **kwargs):
    print target

test(foo='bar', target='baz')

I would presume that target='test' in the aFunc call at the bottom would end up in kwargs (and it does), and I would also presume that **would unpack kwargs in the function call, so target would exist as a keyword argument inside of aFunc. It doesn't. I know that it comes in as a dict, but I need to have that dict unpack in the argument list. Is this possible? In short, is there any way to have *args and **kwargs disappear and have the actual args and kwargs go into the call?

Edit: I threw together a case where unpacking of *args and **kwargs might help:

Let's say I have a function that prints a list:

def printList (inputList=None):
    print inputList

I want to be able to pass no list and have a default list supplied:

def ensureList (listFunc):
    def wrapper (inputList=None):
        listFunc(inputList=inputList or ['a','default','list'])
    return wrapper

@ensureList
def printList (inputList=None):
    print inputList

Now I want to get a bit more complicated with a list repeater:

@ensureList
def repeatList (inputList=None):
    print inputList*2

That works fine. But now I want variable repeating:

@ensureList
def repeatList (times, inputList=None):
    print inputList*times

Now you would be able to say:

repeatList(5)

It would generate the default list and repeat it 5 times.

This fails, of course, because wrapper can't handle the times argument. I could of course do this:

@ensureList
def repeatList (inputList=None, times=1)

But then I always have to do this:

repeatList(times=5)

And maybe in some cases I want to enforce supplying a value, so a non-keyword arg makes sense.

When I first encountered problems like this last year, I thought a simple solution would be to remove the requirements on the wrapper:

def ensureList (listFunc):
    "info here re: operating on/requiring an inputList keyword arg"
    def wrapper (*args, **kwargs):
        listFunc(inputList=inputList or ['a','default','list'])
    return wrapper

That doesn't work, though. This is why I'd like to have args and kwargs actually expand, or I'd like to have a way to do the expansion. Then whatever args and kwargs I supply, they actually fill in the arguments, and not a list and a dict. The documentation in the wrapper would explain requirements. If you pass in inputList, it would actually go in, and inputList in the call back to repeatList from the wrapper would be valid. If you didn't pass in inputList, it would create it in the call back to repeatList with a default list. If your function didn't care, but used *kwargs, it would just gracefully accept it without issue.

Apologies if any of the above is wrong (beyond the general concept). I typed it out in here, untested, and it's very late.

Gary Fixler
  • 5,632
  • 2
  • 23
  • 39
  • 6
    "I need to have that dict unpack in the argument list." – I cannot imagine any reason why you would need this. Could you please enlighten me? – Sven Marnach May 23 '12 at 10:06
  • 5
    If you know which options you're going to get, then why use `**kwargs` at all? – Fred Foo May 23 '12 at 10:06
  • 3
    How to better go about doing what? You don't know what I'm doing. I just asked if I could unpack the list and dict inside the arguments. And it isn't complaining. It's just stating a fact. This site has a very difficult crowd. I ask straightforward questions - is it possible to do this? with a code example - and I *always* am met with people asking "Why on earth would you ever want to do that?" Maybe I don't. I just want to know if I can. – Gary Fixler May 23 '12 at 10:15
  • 3
    @GaryFixler: what you trying to achieve in the question makes no sense, so it's quite natural that people point that out. – vartec May 23 '12 at 10:19
  • @GaryFixler That's because Python code is meant to be readable and there is usually one good way of doing things. If you want to 'just get things done' like you have explained, you should probably look at perl. – jamylak May 23 '12 at 10:26
  • 3
    @jamylak - you're coming across as very abusive. I've been working in Python for 3 years. I solve things all day with Python, but once in awhile I can't figure something out, like this simple thing - I've been told **dict unpacks the dict into key=value pairs, but it doesn't seem to actually work that way, and I've been unable to figure out more about it. The rare, weird things - the things I will probably never use - are the things that I ask here, because everything else I can solve on my own or through google. I don't mean to offend people. I'm trying to understand what's possible. – Gary Fixler May 23 '12 at 10:29
  • @GaryFixler sorry if I'm coming across as abusive, you aren't offending me but I'm just trying to explain why people are reluctant to give answers. If you accept that then what are we arguing about? – jamylak May 23 '12 at 10:32
  • For posterity, 3 years make a big difference. I'm mostly in Haskell these days, and looking over this post again (reminded of it by earning a badge for >1000 views), I hate everything I wrote up there. What a mess of bad ideas :) – Gary Fixler May 09 '15 at 10:46
  • This is a good example of the mantra `explicit is better than implicit` =) – alvas Mar 09 '17 at 10:19

2 Answers2

16

The answer to "why doesn't ** unpack kwargs in function calls?" is: Because it's a bad idea, the person who develop a function does not want local variable to just appear depending on the call arguments.

So, this is not how it's working and you surely do not want python to behave like that.

To access the target variable in the function, you can either use:

def test(target='<default-value>', *args, **kwargs):
    print target

or

def test(*args, **kwargs):
    target = kwargs.get('target', '<default-value>')
    print target

However, if you want a hack (educational usage only) to unpack **kwargs, you can try that:

def test(*args, **kwargs):
    for i in kwargs:
        exec('%s = %s' % (i, repr(kwargs[i])))
    print target
math
  • 2,811
  • 3
  • 24
  • 29
  • 1
    Thank you for answering the question. It appears it is not possible. – Gary Fixler May 23 '12 at 10:19
  • 6
    It is possible, it's just a Bad Idea, like it is 99% of the time someone thinks they want to create variable names dynamically. – Wooble May 23 '12 at 10:21
  • Good answer, but what do you mean by "*args can't be automatically unpacked because there is no variable name associated." – detly May 23 '12 at 10:21
  • Thanks for the update, math. I definitely don't want to do that :) – Gary Fixler May 23 '12 at 10:31
  • @math - that's the best reason against that I've heard. Having an unexpected local variable appear does seem a bad idea. I was thinking more in terms of decorators, where all of your args are passed right on through via *args and **kwargs, but the decorator can do things if particular keywords are given. Decoration is difficult, however, if arguments and keyword-arguments don't match. In some cases I would prefer a decorator to just take anything you give it from any wrapped function, and only do its thing if you supply a particular argument, which the decorator would have documented. – Gary Fixler May 23 '12 at 10:49
  • Keep in mind that the hack will fail horribly if you pass in invalid identifiers in the kwargs dict. – Wooble May 23 '12 at 13:22
  • 1
    That `target = kwargs.get('target', '')` bit is perfect for use in Bottle/Flask type apps where you want to have optional keyword variables in a url – Dan Gayle Aug 24 '13 at 23:40
  • @SvenMarnach this [SO answer](https://stackoverflow.com/a/1463370/307454) shows the way in Python 3. There _must_ be some simpler? – lifebalance Sep 12 '17 at 03:04
  • @lifebalance As noted in this answer, you should not use the version with `exec` in actual code. It's just as provided as a demonstration, but it's a bad idea in practice. The code you linked won't help you, since it would require to know all the keyword arguments in advance. As far as I'm aware, all local variable names need to be known statically at compile time in Python 3, and this is a good thing. – Sven Marnach Sep 12 '17 at 08:23
2

The obvious way for this particular case is

def test(foo=None, target=None):
    print target

test(foo='bar', target='baz')

If you want to access a parameter inside a function by name, name it explicitly in the argument list.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841