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)?
An assignment statement always has a single variable on the left hand side. The value of the expression (which can contain math operators and other variables) on the right of the = sign is stored in the variable on the left.
There are two types of assignment statements: Symbol assignment statements, which define or redefine a symbol in the symbol name space. Register assignment statements, which define or redefine a register name in the symbol name space.
The assignment operator, denoted by the “=” symbol, is the operator that is used to assign values to variables in Python. The line x=1 takes the known value, 1, and assigns that value to the variable with name “x”. After executing this line, this number will be stored into this variable.
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:
PEP 3132 -- Extended Iterable Unpacking
Understanding the asterisk(*) of Python.
Unpacking Argument Lists.
Keyword Arguments
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With