2020
2020

Reputation: 2841

Why does * work differently in assignment statements versus function calls?

In Python, varargs collection seems to work quite differently from how sequence unpacking works in assignment statements. I'm trying to understand the reason for this potentially confusing difference. I'm sure there is a good reason, but what is it?


# Example 1 with assignment statement
a, *b, c = 1,2,3,4
print(b) # [2, 3]

# Example 1 with function call
def foo(a, *b, c):
    print(b)
foo(1,2,3,4)

The function call results in the following error:

Traceback (most recent call last):
  File "<pyshell#309>", line 1, in <module>
    foo(1,2,3,4)
TypeError: foo() missing 1 required keyword-only argument: 'c'

Question 1: Why is b not assigned similar to how it is done in the assignment statement?

# Example 2 with function call
def foo(*x):
    print(x)
    print(type(x))
foo(1,2,3) # (1, 2, 3) <class 'tuple'>

# Example 2 with assignment statement
a, *b, c = 1,2,3,4,5
print(b) # [2, 3, 4]
print(type(b)) # <class 'list'>

Question 2: Why the difference in type (list vs tuple)?

Upvotes: 10

Views: 271

Answers (1)

Ismael Padilla
Ismael Padilla

Reputation: 5566

When used in an assignment, Python will try to make *b match to whatever it needs in order to make the assignment work (this is new in Python 3, see PEP 3132). These are both valid:

a, *b, c = 1,4
print(b) # []

a, *b, c = 1,2,3,4,5 
print(b) # [2, 3, 4]

When used in a function, if *b is the second parameter in the function definition, it will match with the second to last arguments in the function call, if there are any. It's used when you want your function to accept a variable number of parameters. Some examples:

def foo(a, *b):
    print(b)
foo(1) # ()
foo(1,2,3,4,5) # (2,3,4,5)

I recommend you read the following:


On the difference between lists and tuples, the big one is mutability. Lists are mutable, tuples aren't. That means that this works:

myList = [1, 2, 3]
myList[1] = 4
print(myList) # [1, 4, 3]

And this doesn't:

myTuple = (1, 2, 3)
myTuple[1] = 4 # TypeError: 'tuple' object does not support item assignment

The reason why b is a list in this case:

a, *b, c = 1,2,3,4,5 
print(b) # [2, 3, 4]

And not a tuple (as is the case when using *args in a function), is because you'll probably want to do something with b after the assignment, so it's better to make it a list since lists are mutable. Making it a tuple instead of a list is one of the possible changes that were considered before this was accepted as a feature, as discussed in PEP 3132:

Make the starred target a tuple instead of a list. This would be consistent with a function's *args, but make further processing of the result harder.

Upvotes: 5

Related Questions