Function calls and varagrs

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 first call work but the last one doesn't? I mean you shouldn't return an error first? Don't * just unpack an iterable?

+3


source to share


2 answers


As the docs say:

If syntax appears in a function call *expression

, expression

must evaluate iterable. Elements from this iterable are treated as if they were optional positional arguments; if there are positional arguments x1, ..., xN and the expression evaluates to the sequence y1, ..., yM, this is equivalent to calling with M + N positional arguments x1, ..., xN, y1, ..., yM.

A consequence of this is that although the syntax *expression

may appear after some of the keyword arguments, it is processed before the keyword arguments ...




Many people are confused that function definitions have similar, sometimes misleading, syntax.

In a function definition, a parameter-parameter parameter (for example, *args

) appears before any parameters for a keyword only. Of course, the keyword and the default are completely independent, but it's quite common that all keyword-only parameters have default values. Thus, the syntax often looks like def func(a, *args, c=4, **kwargs):

. This can lead to what func(1, *(2,), c=3, **{'d': 4}

is the syntax of the corresponding call, even if it is not. Just remember, which def func(a=1, *args, c, **kwargs)

is perfectly legal and it still makes a

a positional or keyword parameter and a keyword c

only parameter.




If you're wondering how this works specifically in CPython (although other implementations are probably all pretty similar):

The function call itself is compiled to pass a value expression

on the stack, still separated from normal arguments. This is inside the interpreter, in the function call evaluator, where a stack frame is created to execute the function body, where this value is inserted into additional arguments.




This can help you 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 the AST, you should be able to see what (2,)

is still separate during parsing, stored in the named field starargs

.

This will compile 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 this gibberish, but you can see that the tuple is (2,)

loaded onto the stack at offset 12, and it is still on the stack when the opcode CALL_FUNCTION_VAR_KW

is executed. And you can look at this opcode in the docs where it says:

Calls a function. argc

interpreted as CALL_FUNCTION

. The top item on the stack contains a keyword argument dictionary, followed by a tuple of argument variables, followed by explicit keywords and positional arguments.

So the "argument-variable tuple" is still separate.

+2


source


Positional parameters should always appear before named and unpacked parameters.

In the expression:

func(1, c=3, 2, **{'d':4})

      

2

is a positional parameter, and c=3

is a named parameter. It is incorrectly written like this. You must 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})

      

... 1

is the only positional parameter here. c=3

is a named parameter, and *(2,)

and are **{'d':4}

unpacked. All this is valid as long as the positioning parameter is the first.

+5


source







All Articles