Antoine Gallix
Antoine Gallix

Reputation: 816

Unpacking more than one list as argument for a function

If I have a function like:

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

Then why does this works:

f(1,2,3,4)
f(*[1,2,3,4])

But not this:

f(*[1,2] , *[3,4])
    f(*[1,2] , *[3,4])
               ^
SyntaxError: invalid syntax

?

EDIT : For information, the original problem was to replace one of the argument in a function wrapper. I wanted to replace a given member of the inputted *args and tried something like:

def vectorize_pos(f,n=0):
    '''
    Decorator, vectorize the processing of the nth argument
    :param f: function that dont accept a list as nth argument
    '''
    def vectorizedFunction(*args,**kwargs):
        if isinstance(args[n],list):
            return map(lambda x : f( *(args[:n]) , x , *(args[n+1,:]), **kwargs),args[n])
        else:
            return f(*args,**kwargs)
    return vectorizedFunction

That's where the question arose from. And I know there is other way do do the same thing but only wanted to understand why unpacking one sequence worked but not for more.

Upvotes: 22

Views: 11625

Answers (7)

gerrit
gerrit

Reputation: 26445

Starting in Python 3.5, this does work.

PEP 448 was implemented in Python 3.5. Quoting from the PEP, it allows, among other things:

Arbitrarily positioned unpacking operators:

>>> print(*[1], *[2], 3)
1 2 3
>>> dict(**{'x': 1}, y=2, **{'z': 3})
{'x': 1, 'y': 2, 'z': 3}

Upvotes: 20

sabbahillel
sabbahillel

Reputation: 4425

These may help. Note that the analogy is to a variable number of arguments in other languages. This means that once you say you are going to use a variable number of arguments, all following arguments are part of that list (analogy to C or C++ use of varargs).

for example f = [1,2,3,4,5]

def func(a, b, c, d)
  print a, b, c, d

func(f) # Error 1 argument, 4 required

func(*f) # Error 5 arguments 4 required

http://www.python-course.eu/passing_arguments.php

Variable Length of Parameters
We will introduce now functions, which can take an arbitrary number of arguments. Those who have some programming background in C or C++ know this from the varargs feature of these languages. The asterisk "*" is used in Python to define a variable number of arguments. The asterisk character has to precede a variable identifier in the parameter list.

>>> def varpafu(*x): print(x)
... 
>>> varpafu()
()
>>> varpafu(34,"Do you like Python?", "Of course")
(34, 'Do you like Python?', 'Of course')
>>> 

We learn from the previous example, that the arguments passed to the function call of varpafu() are collected in a tuple, which can be accessed as a "normal" variable x within the body of the function. If the function is called without any arguments, the value of x is an empty tuple.

Sometimes, it's necessary to use positional parameters followed by an arbitrary number of parameters in a function definition. This is possible, but the positional parameters always have to precede the arbitrary parameters. In the following example, we have a positional parameter "city", - the main location, - which always have to be given, followed by an arbitrary number of other locations:

>>> def locations(city, *other_cities): print(city, other_cities)
... 
>>> locations("Paris")
('Paris', ())
>>> locations("Paris", "Strasbourg", "Lyon", "Dijon", "Bordeaux", "Marseille")
('Paris', ('Strasbourg', 'Lyon', 'Dijon', 'Bordeaux', 'Marseille'))
>>>

http://docs.python.org/2.7/reference/expressions.html

If the syntax *expression appears in the function call, expression must evaluate to an iterable. Elements from this iterable are treated as if they were additional positional arguments; if there are positional arguments x1, ..., xN, and expression evaluates to a sequence y1, ..., yM, this is equivalent to a call with M+N positional arguments x1, ..., xN, y1, ..., yM.

A consequence of this is that although the *expression syntax may appear after some keyword arguments, it is processed before the keyword arguments (and the **expression argument, if any – see below). So:

>

>>> def f(a, b): ...  print a, b ...
>>> f(b=1, *(2,)) 2 1
>>> f(a=1, *(2,)) Traceback (most recent call last):   File "<stdin>", line 1, in ? TypeError: f() got multiple values for keyword argument
'a'
>>> f(1, *(2,)) 1 2

It is unusual for both keyword arguments and the *expression syntax to be used in the same call, so in practice this confusion does not arise.

If the syntax **expression appears in the function call, expression must evaluate to a mapping, the contents of which are treated as additional keyword arguments. In the case of a keyword appearing in both expression and as an explicit keyword argument, a TypeError exception is raised.

Upvotes: 3

holdenweb
holdenweb

Reputation: 37003

It doesn't work because it's invalid syntax - that is to say, it isn't actually Python although it looks like it.

Only one starred parameter is allowed in Python 2 function signatures, and it must follow any positional parameters and precede any keyword parameters. Similarly only one double-starred argument is allowed, and it must follow all keyword parameters in the signature. If you have multiple lists of arguments you would like to submit you will indeed have to create a single list from them first.

In Python 3 it is also possible to use a star on its own to indicate that any following parameters are so-called keyword-only parameters, but I don't think we need get into that just yet.

Upvotes: 4

ndpu
ndpu

Reputation: 22561

You can concatenate lists:

>>> f(*[1,2]+[3,4])
1 2 3 4

or use itertools.chain:

>>> from itertools import chain
>>> f(*chain([1,2], [3,4]))
1 2 3 4

Upvotes: 5

thefourtheye
thefourtheye

Reputation: 239453

Because, as per the Function call syntax, this is how the argument list is defined

argument_list ::=  positional_arguments ["," keyword_arguments]
                     ["," "*" expression] ["," keyword_arguments]
                     ["," "**" expression]
                   | keyword_arguments ["," "*" expression]
                     ["," keyword_arguments] ["," "**" expression]
                   | "*" expression ["," keyword_arguments] ["," "**" expression]
                   | "**" expression

So, you can pass only one * expression per function call.

Upvotes: 20

Guy
Guy

Reputation: 624

This would work instead.

>>>def f(a,b,c,d):
         print('hello') #or whatever you wanna do. 
>>>f(1,2,*[3,4])
hello

The reason that doesn't work is that Python implement this using this

One list unpacks, and following the semantics, any argument after that must be a named keyword argument( or a dictonary passing named keyword arguments through**)

By constrast, this would work.

>>>def f(a,b,c,k):
       pass
>>>f(1,*[2,3],k=4)

Upvotes: 5

Peter Westlake
Peter Westlake

Reputation: 5036

The * here isn't acting as an operator. It's more like part of the syntax of function calls, and it only allows for certain limited possibilities. It would have been possible to define the language so that you could do what you want (I've done it!), but that wasn't the choice that was made.

Upvotes: 3

Related Questions