Reputation: 8030
def func(a, b, c, d): print(a, b, c, d)
func(1, c=3, *(2,), **{'d':4})
func(1, c=3, 2, **{'d':4})
Why does the former call work but the latter not? I mean shouldn't the first return an error too? Doesn't * simply unpack an iterable?
Upvotes: 3
Views: 81
Reputation: 365975
As the docs say:
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…
At lot of people are confused by the fact that function definitions have a similar, sometimes misleadingly-similar, syntax.
In a function definition, a variable-argument parameter (like *args
) comes before any the keyword-only parameters. Of course being keyword-only and having a default value are completely independent, but it's pretty common that the keyword-only parameters all have default values. So, the syntax often looks like def func(a, *args, c=4, **kwargs):
. Which can lead you to expect func(1, *(2,), c=3, **{'d': 4}
to be the matching call syntax, even though it isn't. Just remember that def func(a=1, *args, c, **kwargs)
is perfectly legal, and it still makes a
a positional-or-keyword parameter and c
a keyword-only parameter.
If you're interested how this works specifically in CPython (although other implementations are probably all pretty similar):
The function call itself gets compiled to pass the value of the expression
on the stack, still separate from the normal arguments. It's inside the interpreter, in the function-call evaluator, where the stack frame for the function body's execution gets built, where that value is exploded into extra arguments.
It may help to see how CPython parses and compiles this code:
>>> astpp(ast.parse("func(1, c=3, *(2,), **{'d':4})"))
Module(
body=[
Expr(
value=Call(
func=Name(id='func', ctx=Load()),
args=[Num(n=1)],
keywords=[keyword(arg='c', value=Num(n=3))],
starargs=Tuple(elts=[Num(n=2)], ctx=Load()),
kwargs=Dict(keys=[Str(s='d')], values=[Num(n=4)])))])"
Even if you don't understand ASTs, you should be able to see that the (2,)
is still separate at parse time, stored in a field named starargs
.
This gets compiled to this bytecode:
2 0 LOAD_GLOBAL 0 (func)
3 LOAD_CONST 1 (1)
6 LOAD_CONST 2 ('c')
9 LOAD_CONST 3 (3)
12 LOAD_CONST 7 ((2,))
15 BUILD_MAP 1
18 LOAD_CONST 5 (4)
21 LOAD_CONST 6 ('d')
24 STORE_MAP
25 CALL_FUNCTION_VAR_KW 257
28 POP_TOP
29 LOAD_CONST 0 (None)
32 RETURN_VALUE
You probably don't understand all that gibberish, but you can see that the tuple (2,)
is being loaded onto the stack at offset 12, and it's still on the stack when the opcode CALL_FUNCTION_VAR_KW
gets executed. And you can look that opcode up in the docs, where it says:
Calls a function.
argc
is interpreted as inCALL_FUNCTION
. The top element on the stack contains the keyword arguments dictionary, followed by the variable-arguments tuple, followed by explicit keyword and positional arguments.
So, the "variable-arguments tuple" is still separate.
Upvotes: 2
Reputation: 134571
Positional parameters must always appear before named and unpacked parameters.
In the expression:
func(1, c=3, 2, **{'d':4})
The 2
is a positional parameter while c=3
is a named parameter. It is invalid written this way. You have to move the named parameter after all positional parameters.
func(1, 2, c=3, **{'d':4})
On the other hand, the expression:
func(1, c=3, *(2,), **{'d':4})
is valid. 1
is the only positional parameter here. c=3
is a named parameter and *(2,)
and **{'d':4}
are unpacked. It's all valid as long as the positional parameter comes first.
Upvotes: 5