2

I've been trying to understand the algorithm Python follows when assigning values to function parameters and it's just been very confusing. I would like to understand the algorithm better so that I can actually know what values get assigned to what parameters and when. There are positional arguments, keyword arguments, *args, *kwargs, and keyword only arguments. How do these all get assigned and in what order?

Take the following code snippet for one specific example:

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

If I call this function in the following format, it is going to run normally:

func(c=3, *(1, 2)) # prints 1 2 3

However if I run it using this it gives a SyntaxError:

func(c=3, 1, 2)

How are the two forms of the call different? In the first call, the tuple gets unpacked into individual arguments. Isn't it the same as being converted into the second form? If that's what happens then the second form should work too, but it doesn't.

So what's actually happening here?

2 Answers 2

1

There is nothing special to it: It's just how Python works.

Python prohibits passing in positional parameters after named parameters. Just like you can't define a function with mandatory arguments after optional arguments or after keyword-only arguments. However when calling a function it's more a matter of readability to use positional arguments before named arguments, while for function definitions it's actually a matter of "removing ambiguity". But it definitely has some symmetry the way it works.

You can bypass this as you've seen by using unpacking, but it comes at a price: It's a bit slower because of the additional work:

def func(a, b, c):
    pass

%timeit func(c=3, *(1, 2))
# 810 ns ± 16.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit func(1, 2, c=3)
# 383 ns ± 1.47 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

That's actually very important to remember: If you try to fight Pythons language you pay with performance.


In case you are interested it basically works just like if your function had only *args and **kwargs as arguments. It collects all positional arguments in *args and all named arguments in **kwargs and then tries to "bind" them to the actual function arguments.

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

3 Comments

But why isn't it valid syntax? It can just assign the keyword argument and then assign the rest of the values to any empty parameters in order like it does for the second call type in my example.
Because that's how Pythons language syntax works. Unpacking arguments is just different from passing in the positional arguments.
@AsadMoosvi I updated the answer. I think the original answer missed the point. Thank you for the comment :)
0

In Python, when a function is called with parameters which have *, it assigns these values to the parameters in the order they are defined in the function definition.

For Ex:

def func(a, b, c):
    print(a, b, c)
func(a=3, *(1, 2))

This will result in

TypeError: func() got multiple values for keyword argument 'a'

OR

def func(a, b, c):
    print(a, b, c)
func(b=3, *(1, 2))

This will result in

TypeError: func() got multiple values for keyword argument 'b'

So in this case, variable c is explicitly assigned and the rest of the parameters are assigned from the variable length argument list.

Coming to second part of the question, Python allows you to explicitly assign a data to a parameter only after all the values are passed.Following code will work fine

def func(a, b, c):
        print(a, b, c)
    func(3, 1,c=2))

but notfunc(3,b=1,2) or func(a=3,1,2)

You can find more info about this here What does ** (double star/asterisk) and * (star/asterisk) do for parameters?

Comments

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.