Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why doesn't ** unpack kwargs in function calls?

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.

like image 391
Gary Fixler Avatar asked May 23 '12 10:05

Gary Fixler


1 Answers

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
like image 89
math Avatar answered Sep 27 '22 19:09

math