> > Can you explain why these two (apparently) logical assignment processes > have been designed to realise different result-objects?
The reason is because of the conventions chosen in PEP 3132, which implemented the feature in the first place. It was considered to return a tuple for the consistency w/ *args that you initially expected, but the author offered the following reasoning: > 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. So, it was essentially a practicality > purity situation, where it was considered to be more useful to be able to easily transform the result rather than being consistent with *args. If it resulted in a tuple, it would be immutable; this IMO makes sense for *args, but not necessarily for * unpacking in assignments. The syntax is highly similar, but they are used for rather different purposes. That being said, I can certainly understand how the behavior is surprising at first. There's a bit more details in the mailing list discussion that started the PEP, see https://mail.python.org/pipermail/python-3000/2007-May/007300.html. On Tue, Jul 7, 2020 at 2:04 AM dn via Python-list <python-list@python.org> wrote: > TLDR; if you are a Python 'Master' then feel free to skim the first part > (which you should know hands-down), until the excerpts from 'the manual' > and from there I'll be interested in learning from you... > > > Yesterday I asked a junior prog to expand an __init__() to accept either > a series of (>1) scalars (as it does now), or to take similar values but > presented as a tuple. He was a bit concerned that I didn't want to > introduce a (separate) keyword-argument, until 'we' remembered > starred-parameters. He then set about experimenting. Here's a dichotomy > that surfaced as part of our 'play':- > (my problem is: I can't (reasonably) answer his question...) > > > If you read this code: > NB The print-ing does not follow the input-sequence, because that's the > point to be made... > > >>> def f( a, *b, c=0 ): > ... print( a, type( a ) ) > ... print( c, type( c ) ) > ... print( b ) > ... > >>> f( 1, 'two', 3, 'four' ) > > [I had to force "c" to become a keyword argument, but other than that, > we'll be using these three parameters and four argument-values, again] > > > Question 1: did you correctly predict the output? > > 1 <class 'int'> > 0 <class 'int'> > ('two', 3, 'four') > > Ahah, "c" went to default because there was no way to identify when the > "*b" 'stopped' and "c" started - so all the values 'went' to become "b" > (were all "consumed by"...). > > Why did I also print "b" differently? > Building tension! > Please read on, gentle reader... > > > Let's make two small changes: > - amend the last line of the function to be similar: > ... print( b, type( b ) ) > - make proper use of the function's API: > >>> f( 1, 'two', 3, c='four' ) > > > Question 2: can you predict the output of "a"? Well duh! > (same as before) > > 1 <class 'int'> > > > Question 3: has your predicted output of "c" changed? Yes? Good! > (Web.Refs below, explain; should you wish...) > > four <class 'str'> > > > Question 4: can you correctly predict the content of "b" and its type? > > ('two', 3) <class 'tuple'> > > That makes sense, doesn't it? The arguments were presented to the > function as a tuple, and those not assigned to a scalar value ("a" and > "c") were kept as a tuple when assigned to "b". > Jolly good show, chaps! > > (which made my young(er) colleague very happy, because now he could see > that by checking the length of the parameter, such would reveal if the > arguments were being passed as scalars or as a tuple. > > Aside: however, it made me think how handy it would be if the > newly-drafted PEP 622 -- Structural Pattern Matching were available > today (proposed for v3.10, https://www.python.org/dev/peps/pep-0622/) > because (YAGNI-aside) we could then more-easily empower the API to > accept other/more collections! > > > Why am I writing then? > > Because during the same conversations I was > 'demonstrating'/training/playing with some code that is (apparently) > very similar - and yet, it's not. Oops! > > > Sticking with the same, toy-data, let's code: > > >>> a, *b, c = 1, 'two', 3, 'four' > >>> a, type( a ) > >>> c, type( c ) > >>> b, type( b ) > > > Question 5: what do you expect "a" and "c" to deliver in this context? > > (1, <class 'int'>) > ('four', <class 'str'>) > > Happy so far? > > > Question 6: (for maximum effect, re-read snippets from above, then) what > do you expect from "b"? > > (['two', 3], <class 'list'>) > > List? A list? What's this "list" stuff??? > When "b" was a parameter (above) it was assigned a tuple! > > > Are you as shocked as I? > Have you learned something? > (will it ever be useful?) > Has the world stopped turning? > > > Can you explain why these two (apparently) logical assignment processes > have been designed to realise different result-objects? > > > NB The list cf tuple difference is 'legal' - at least in the sense that > it is documented/expected behavior:- > > Python Reference Manual: 7.2. Assignment statements > Assignment statements are used to (re)bind names to values and to modify > attributes or items of mutable objects: > ... > An assignment statement evaluates the expression list (remember that > this can be a single expression or a comma-separated list, the latter > yielding a tuple) and assigns the single resulting object to each of the > target lists, from left to right. > ... > A list of the remaining items in the iterable is then assigned to the > starred target (the list can be empty). > https://docs.python.org/3/reference/simple_stmts.html#assignment-statements > > > Python Reference Manual: 6.3.4. Calls > A call calls a callable object (e.g., a function) with a possibly empty > series of arguments: > ... > If there are more positional arguments than there are formal parameter > slots, a TypeError exception is raised, unless a formal parameter using > the syntax *identifier is present; in this case, that formal parameter > receives a tuple containing the excess positional arguments (or an empty > tuple if there were no excess positional arguments). > https://docs.python.org/dev/reference/expressions.html#calls > -- > Regards, > =dn > -- > https://mail.python.org/mailman/listinfo/python-list > -- https://mail.python.org/mailman/listinfo/python-list