10

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)?

Boann
  • 47,128
  • 13
  • 114
  • 141
2020
  • 2,770
  • 1
  • 20
  • 39
  • 6
    You should read through all related [PEPs](https://www.python.org/dev/peps/) to find explanations, why Python features are as they are. – Klaus D. Jun 25 '19 at 23:17
  • 1
    In functions the supported format is `func(a, b, *args, **kwargs)` where *args, is a tuple and **kwargs is a dict. You can't add a positional argument `c` after variadic `*b`. Positional arguments are for C functions and there is a pep with `func(a,b,/)` format for supporting it on python code – geckos Jun 26 '19 at 00:03
  • Positional only arguments* – geckos Jun 26 '19 at 00:21

1 Answers1

5

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.

Ismael Padilla
  • 4,666
  • 4
  • 23
  • 33
  • 1
    print(b) should be [2, 3, 4] and not [2,3,4,5] – 2020 Jun 26 '19 at 00:23
  • Interestingly, in the medium article you quoted, the author also got confused and said: "The arguments passed as positional are stored in a list called args" ! This possibility for confusion is what arises my question. – 2020 Jun 26 '19 at 00:47
  • While I understand the difference between list and tuple, my question is why List was chosen as the type in case of the * in assignment statement and tuple was chosen as the type in case of * in function call. – 2020 Jun 26 '19 at 00:52
  • 1
    This is because you'll probably want to do something with the list after the assignment, and a list is better than a tuple in this case because it's mutable. Making it a tuple instead of a list is one of the possible changes that was considered, as discussed in [PEP 3132](https://www.python.org/dev/peps/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." – Ismael Padilla Jun 26 '19 at 01:05
  • 1
    Can you pls incorporate this latest comment into the answer ? I think this really answers my question2. – 2020 Jun 26 '19 at 01:21
  • 1
    The other reason for the behavior where `c` doesn't get assigned when you call `def foo(a, *b, c):` is because they wanted this scenario to allow you to implement keyword only arguments. Keyword only arguments are useful, in that they prevent users from passing them accidentally, and allow stuff like Python 3's `print` function to be implemented in Python while allowing the interpreter to do most of the work parsing arguments (as opposed to accepting `**kwargs` and having to do all the keyword parsing yourself). Allows you to write your own `def print(*args, sep=' ', end='\n', ...etc...):` – ShadowRanger Jun 26 '19 at 01:36