New assignmens ...

Christman, Roger Graydon dvl at psu.edu
Wed Oct 27 11:05:23 EDT 2021



On 27/10/2021 8:28, Anton Pardon wrote:
>>> Suppose I would like to write a loop as follows:
>>.   >    while ((a, b) := next_couple(a, b))[1]:
>>   >        do needed calculations
>>
>>
>>> What I can do is write it as follows:
>>>      while [tmp := next_couple(a,b), a := tmp[0], b := tmp[1]][-1]:
>>   >        do needed calculations
>>
>>> I really don't see what is gained by "forcing" me to right the second code over the first.
>> No, nobody is forcing you to write it the second way over the first.
>> Nobody is forcing you to use the walrus operator at all!
>>
>> Instead, I would recommend something more like:
>>
>>     while b:
>>           do needed calculations
>>           (a,b) = next_couple(a,b)

> But AIU the walrus operator was introduced so we no longer needed, to write such code,
> with the calculation of the next candidate at the bottom and the test at the top.
> You just confirmed the walrus operator is not very useful once the next candidate is
> no longer just a name.

I must disagree with the first sentence here regarding why the walrus operator was introduced.
I read through the PEP, and saw nothing about that in the Rationale.   I do see the loop-and-a-half
example, but that actually had its next candidate value near the top (just before a break).

I'm going to provide two loop-and-a-half segments to illustrate my interpretation
of this PEP and the purpose of the walrus operator:

Without the walrus:

total = 0
value = input()
while value >= 0:
     total += value
     value = input()
print(total)

With the walrus:

total = 0
while (value := input()) > 0:
     total += value
print(total)

In terms of the PEP -- I want to compare each input value to 0 for the loop test,
but I also want to preserve that value, to add to the total.   There is a subexpression
(input()) within a larger expression (compared to 0) that I wish to name for reuse.

Now contrast with this example:

Without the walrus:

replay = True
while replay:
    play_game()
    replay = input("Play again? ") in ['y','Y','yes','Yes']

(I think it silly to ask about playing again at first).

With the walrus:

replay = None
while replay==None or (replay := input("Play again? ") in ['y','Y','yes','Yes']:
     play_game()

To use the walrus operator here, I have to fabricate a value that would
allow me to bypass the input operation, that cannot be otherwise produced.
I do not find this second version any clearer or more intuitive than the first
(and the PEP did emphasize the value of clarity).

Now reading this in terms of the PEP, where I subexpression (the input)
in a larger expression (seeing if it is the list), I do not really seem to have
a compelling reason to name the value of that subexpression, since I am
not using it for any other purpose, so I could keep the input in the while
condition without using the walrus operator:

first_play = True
while first_play or input("Play again? ") in ['y','Y','yes','Yes']:
    play_game()
    first_play = False

I do not find this particularly clearer or better than my first version
with the test at the bottom, again since it requires me to do some extra
machinery to avoid the undesired input before the first repetition.
But I might also still argue that this is a little bit clearer than the
walrus alternative above.

My claim from these two examples is that the walrus is helpful to
reduce duplicating or recomputing subexpressions, not simply just
to facilitate code movement.   It is not at all useful for a while loop
condition if the subexpression you wish to evaluate depends on
whether or not this is the first iteration of the loop.

Which then gets me back to your own illustrated use-case for
which I had raised a few questions/objections to before:

while ((a, b) := next_couple(a, b))[1]:
       do needed calculations

What is the name of the value you wish to test?    b
What is this actually testing?   element [1] of a tuple

So already the code is unclear (and was made worse
when you said the existing walrus operator forced you
to make a list of three elements, two assigned with the
walrus, and then using a subscript of [-1] to get what you wanted.
My proposed solution explicitly tested b, since that
seemed to be what was of interest:

 while b:
        do needed calculations
         (a,b) = next_couple(a,b)

To which you had replied:
> But AIU the walrus operator was introduced so we no longer needed, to write such code,
> with the calculation of the next candidate at the bottom and the test at the top.
> You just confirmed the walrus operator is not very useful once the next candidate is
> no longer just a name.

Okay, I would be happy to confirm my belief that the walrus is not very useful
when the thing you wish to test is not the same as the thing you with to assign.

But to relate this use case to my earlier loop-and-a-half examples:

(a,b) := next_couple(a,b)

presumes that there is are values for a and b to begin with for that first call to next_couple.
How do you initialize them correctly?

If you initialize them to the "first" couple (whatever that is), then your loop will
start off finding the second couple and you would be missing your first iteration.

Do you put special code in next_couple() to recognize that the provided arguments
are actually the first couple so it can return those unmodified, but then require its
own mental note not to give you an infinite loop forever returning that first couple?

Do you have to define a special case such as (a,b) = (0,0) or (None,None) to tell
next_couple that you really want the first one?   That seems a little counter-intuitive
to have something named "next" need a special input to mean "first",

Not to mention whatever you use to initialize is probably not going to be very meaningful or
clear on its own terms.  In my earlier note, I objected to "(ctr := ctr-1) > 0" on the grounds
that it required initializing the counter to 11 when you really wanted to count down from 10.

I favor using the walrus operator when it actually makes the code simpler and clearer;
I don't favor using it willy-nilly just because you wish to do an assignment operation
inside of a test condition, if its usage makes the code less clear.   I interpret the PEP
has using the walrus to define a name for a particular value that would be both tested
and reused, and not that it would be used to assign to a value that was not being
tested (such as the a part of (a,b)).

Or in short, I do not find your choice of use-case really lines up with the rationale
given for walrus operator.   You are free to disagree with me, but fortunately, we have
one of those PEP authors participating in this list regularly if you want his opinion.

Roger Christman
Pennsylvania State University




More information about the Python-list mailing list