22

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.

Antoine Gallix
  • 662
  • 1
  • 11
  • 28
  • 1
    possible duplicate of [How to unpack multiple tuples in function call](http://stackoverflow.com/questions/10564801/how-to-unpack-multiple-tuples-in-function-call) – zhangxaochen Feb 28 '14 at 14:40
  • Maybe what you really wanted was `f((a,b), (c,d))`? I have never seen this before, but I can't imagine any case where I'd want to pass in multiple anonymous arguments this way, and making the argument itself a tuple wouldn't be more appropriate. – Corley Brigman Feb 28 '14 at 14:48
  • 2
    Not a duplicate. The focus of that question was how to make it work. This is asking the reasoning behind _why_ it doesn't work. It's subtle but I think worth it's own question. – Guy Feb 28 '14 at 14:49
  • @CorleyBrigman He probably was bored and programming for leisure and came across something he thought should work, and this puzzled him. – Guy Feb 28 '14 at 14:49

7 Answers7

20

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.

thefourtheye
  • 233,700
  • 52
  • 457
  • 497
  • 1
    @thefourtheye Accurate and very good answer. I don't know why I bothered posting. +1 – Guy Feb 28 '14 at 14:51
  • 4
    What's surprising about that is that something nasty like `f(5, *[1], c=3, **{'d':5})` actually works. I didn't know that, I always thought you'd have to write `*` and `**` pseudoarguments right next to each other. – Niklas B. Feb 28 '14 at 14:55
  • @NiklasB. nope. The only rule is named arguments after `*` arguments. – Guy Feb 28 '14 at 15:07
  • 1
    @Sabyasachi: That's not true, you can do `f(b=1,c=2,d=3,*[1])` according to the syntax that is just above this comment – Niklas B. Feb 28 '14 at 15:13
  • @NiklasB. I ran that code now, `f(a=1,b=2,*[3,4])` with four arguments, and it raises a `TypeError`. What version are you using? – Guy Feb 28 '14 at 15:18
  • @NiklasB. you _can_ do `**` not `*` – Guy Feb 28 '14 at 15:20
  • @NiklasB. my apologies. Point ceded. – Guy Feb 28 '14 at 15:28
19

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}
gerrit
  • 24,025
  • 17
  • 97
  • 170
  • It's nice you've added this here, after a comment already pointed out to a discussion which includes a reference to this PEP, but this is absolutely not an answer to the question. – Bach Feb 28 '14 at 15:34
  • 1
    @Bach I didn't see a comment referencing this PEP. Whereas I agree that it is not technically an answer to the question, I think it is sufficiently relevant to be posted as one. I'll let the community decide (personally, I think it should still be an answer, and not a comment). – gerrit Feb 28 '14 at 15:37
  • It's marked as Final for 3.5 when I just checked here: https://www.python.org/dev/peps/pep-0448/ – Dannid Aug 25 '15 at 17:31
5

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
ndpu
  • 22,225
  • 6
  • 54
  • 69
  • 4
    his question is why it doesn't work. Although you answered _how_ to make it work. +1 for that. – Guy Feb 28 '14 at 14:40
5

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)
Guy
  • 604
  • 5
  • 21
4

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.

holdenweb
  • 33,305
  • 7
  • 57
  • 77
3

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.

Peter Westlake
  • 4,894
  • 1
  • 26
  • 35
  • nowhere did he mention that the thought `*` was working as an operator. Answer not on topic, and doesn't address OP's question. -1 – Guy Feb 28 '14 at 14:56
  • @Sabyasachi: I think OPs usage of the * implies that he/she thinks it in fact *is* some kind of operator, while it's not. I think this answer is valid and correct – Niklas B. Feb 28 '14 at 14:57
  • @NiklasB. That's fair enough. It's just our opinions. Whatever the community decides as a whole. Some people seem to think downvotes as personal, it isn't though. :) – Guy Feb 28 '14 at 14:58
  • @NiklasB. also tiny nitpick. The docs to call it an operator. *checkmate* – Guy Feb 28 '14 at 15:00
3

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.

sabbahillel
  • 4,357
  • 1
  • 19
  • 36
  • good but maybe a little too much for (what i assume to be) someone comparatively new to python. Don't you think? – Guy Feb 28 '14 at 15:08