Python - Why is my list turning into a tuple when passing it to a method with varargs (keywordonly)

9.3k views Asked by At

I pass a list to a method which accepts multiple params (*values). If I pass multiple values separated by "," its all fine, but if I pass a list *values turns into a tuple and doesnt iterate the values and the only element is the list I would like to iterate. Can somebody explains that? Is there some way to work around that, that both ways work?

My method:

    def accept(*values): 
        for value in values:
            #do something with value

Works:

foo.accept(value1, value2, value3)

Doesnt work:

values = [value1, value2, value3]
foo.accept(values)

Thank you for any help

4

There are 4 answers

0
mgilson On BEST ANSWER

You need to unpack the list in the function call with the * operator:

foo.accept(*values)
1
Anurag Baddam On

How the multiple parameters feature in python works is that you can pass in a variable number of arguments to the function, and it will automatically turn them into a list (so you don't have to manually) and then execute the code in the function. So when you do foo.accept(value1, value2, value3), it creates a list [value1, value2, value3] and labels this list as the argument to your function, and then you code iterates through the list. However, when you do

values = [value1, value2, value3]
foo.accept(values)

the variable arguments (varargs) feature will wrap your values list into another list so now the argument to your function is [[value1, value2, value3]] and when your for loop does the iteration, it only goes through the one element in this bigger list (the one element being your values list).

Hope this helps!

0
textshell On

This syntax is explicitly for taking an arbitrary number of arguments a input

def accept1(*values): 
    #do something with values

For any given number of arguments it's exactly the same as writing, e.g.:

def accept2(arg1, arg2, arg3):
    values = (arg1, arg2, arg3)
    #do something with values

To call a function with 3 arguments you can write for both as usual:

accept1(1,2,3)
accept2(1,2,3)

But if you have the arguements in a list or tuple you could do

args = (1,2,3)
accept1(args[0], args[1], args[3])
accept2(args[0], args[1], args[3])

This of course is inflexible and a lot to write so python has a shortcut that again works for an arbitrary number of arguments:

accept1(*args)
accept2(*args)

So in both cases that * denotes a generic way to handle multiple parameters by either

  • packing individual arguments of a function into a list (in def), or
  • unpacking a list into individual arguments of a function.
1
AudioBubble On

From the Python 2.7.10 documents (slight edit):

4.7.3. Arbitrary Argument Lists

Finally, the least frequently used option is to specify that a function can be called with an arbitrary number of arguments, i.e. using *args. These arguments will be wrapped up in a tuple.

Another point that might help in our understanding is that in Python the parenthesis are redundant in the following line of code:

a = (1, 2, 3)

It would have been enough to write:

a = 1, 2, 3

So onto answering the question. When you have foo.accept(1, 2, 3) the variable arguments get wrapped into a tuple. To verify this step through the code using your favorite debugger into accept() and find that there the arguments have indeed been wrapped in a tuple:

values = (1, 2, 3)

This is what is expected by definition, and the iteration goes over the three elements of the tuple 1, 2, and 3.

On the other hand when you call foo.accept([1, 2, 3]), again, by definition the argument gets wrapped in a tuple. To verify this step through the code into accept() and find:

values = ([1, 2, 3])

This might be unexpected, but it is the way *args are passed. Now the iteration goes over the one element in the tuple [1, 2, 3].

A workaround is usually specific with what is trying to be achieved. But perhaps something as simple as:

foo.accept(1, 2, 3)

or:

foo.accept(*[1, 2, 3])