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

Reply via email to