Questioning the effects of multiple assignment

Kyle Stanley aeros167 at gmail.com
Tue Jul 7 03:44:45 EDT 2020


>
> 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 at 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
>


More information about the Python-list mailing list