This question is going to be rather long, so I apologize preemptively.
In Python we can use * in the following three cases:
I. When defining a function that we want to be callable with an arbitrary number of arguments, such as in this example:
def write_multiple_items(file, separator, *args):
file.write(separator.join(args))
In this case, the excess positional arguments are collected into a tuple.
II. The reverse case is when the arguments are already in either a list or a tuple and we wish to unpack them for a function call requiring separate positional arguments, such as in this example:
>>> range(3, 6) # normal call with separate arguments
[3, 4, 5]
>>> args = [3, 6]
>>> range(*args) # call with arguments unpacked from a list
[3, 4, 5]
III. Starting with Python 3, * is also used in the context of extended list or tuple unpacking, such as in this example for tuples:
>>> a, *b, c = range(5)
>>> b
[1, 2, 3]
or for lists:
>>> [a, *b, c] = range(5)
>>> b
[1, 2, 3]
In both cases, all items from the iterable being unpacked that are not assigned to any of the mandatory expressions are assigned to a list.
So here's the question: in case I the extra args are collected into a tuple, while in case III the extra items are assigned to a list. Whence this discrepancy? The only explanation I could find was in PEP 3132 which says that:
Possible changes discussed were:
[...]
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.
However, from a pedagogical perspective this lack of consistency is problematic, especially given that if you wanted to process the result, you could always say list(b) (assuming b in the above examples was a tuple). Am I missing something?