1

I tried to use * and ** to pass any number of arguments to a function. In "Learning Python" authored by Mark Lutz, it says to follow the order of positional (value) first, then a combination of keyword arguments (name=value) and *sequence, followed by **dict. However, I found that the positional arguments need to come first if present, but the rest of three, to certain extent, can be mixed in order.
Code keywords3.py:

def     func(a, b=1, *pArgs, **kwArgs): 
        print("a = {0}, b = {1}".format(a,b))
        print("Positional args = {}".format(pArgs))
        print("Keyword args = {}".format(kwArgs))

By trial-and-error,

[1] Between keywords and **dict, they can be in any order...

>>> import keywords3 as j

>>> j.func(b = 3, **{'a':2,'c':4,'d':5})
a = 2, b = 3
Positional args = ()
Keyword args = {'d': 5, 'c': 4}
>>> j.func( **{'a':2}, b = 3, **{'c':4})
a = 2, b = 3
Positional args = ()
Keyword args = {'c': 4}

[2] Between positional args and *sequence, they can be in any order...

>>> j.func(*(2, 3), 4, *(5, 6))
a = 2, b = 3
Positional args = (4, 5, 6)
Keyword args = {}
>>> j.func(2, *(3, 4), 5, *(6,7), **{'c':8})
a = 2, b = 3
Positional args = (4, 5, 6, 7)
Keyword args = {'c': 8}

[3] In general, positional or *sequence arguments need to appear before keyword or **dict arguments.

>>> j.func(*(3, 4), 5, *(6,7), d=15, **{'c':8}, e=16)
a = 3, b = 4
Positional args = (5, 6, 7)
Keyword args = {'e': 16, 'd': 15, 'c': 8}

>>> j.func(d=15, 5, *(6,7), **{'c':8}, e=16)
  File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument

>>> j.func(**{'a':2}, 5, *(6,7), **{'c':8}, e=16)
  File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument unpacking

>>> j.func(**{'a':2}, *(6,7), **{'c':8}, e=16)
  File "<stdin>", line 1
SyntaxError: iterable argument unpacking follows keyword argument unpacking

[4] One exception is that iterable argument unpacking *(6,7) following keyword argument is ok...

>>> j.func(f=5, *(6,7), **{'c':8}, e=16)
a = 6, b = 7
Positional args = ()
Keyword args = {'e': 16, 'f': 5, 'c': 8}

Are these observations correct? Please comment.

2
  • 2
    The grammar rules are available at: docs.python.org/3.6/reference/… Commented Aug 19, 2018 at 6:41
  • 1
    You confuse list/dictionary unpacking that happens at the time of function call, and parameter lists/dictionaries that are used at the time of function definition. Both use the same notation (* and **) but have a totally different meaning. For example, f(*(1,2),3) is expanded to f(1,2,3). 1 and 2 are still positional arguments. Likewise, f(**{'c':8},e=16) becomes f(c=8,e=16), two named arguments. Commented Aug 19, 2018 at 6:41

1 Answer 1

3

There is one single rule consistent with all your examples: positional arguments go before named arguments.

In all your examples, * and ** are unpacking operators. So, for example, when you write

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

the language gets

f(1,2,3,4)

They are all positional arguments, the language doesn't know the difference. Similarly for the ** operator.

However, when you violate that only rule, eg

j.func(**{'a':2}, 5, *(6,7), **{'c':8}, e=16)

you get an error, becuase, in this example, **{'a':2} is equivalent to a=2, which precedes the positional argument 5.

Sign up to request clarification or add additional context in comments.

3 Comments

Not quite. f(a=1, *b) has a keyword argument before a * unpacking, but it's allowed. It means the same thing as the more intuitive f(*b, a=1), and f(*b, a=1) used to be disallowed.
Thanks all for the excellent explanations. What @user2357112 said about f(a=1, *b) being allowed is what I observed in [4] exception. Compared with f(**a, *b) which is not allowed due to the 'single rule', this kind of breaks the one single rule. True?
@LeonChang: the language somehow has to know which value corresponds to which variable. In this case, it works because your named argument happens to be the first one. Still, I would think that this would give an error (and I bet, so would many other python programmers). Regardless of what is valid, readability matters, so don't do it. Use either * when a function really accepts a collection of items, or ** when you really have a dict/json-like object coming from some other function. Avoid using both, and, in most cases, don't use either.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.