[Python-ideas] Inline assignments using "given" clauses
Steven D'Aprano
steve at pearwood.info
Sat May 12 14:27:35 EDT 2018
Part 2.
On Sat, May 12, 2018 at 08:16:07AM -0700, Neil Girdhar wrote:
> I love given compared with := mainly because
[...]
> * Python has a reputation for being working pseudocode, and given reads
> like pseudocode. := needs to be deciphered by comparison especially in the
> complicated cases where multiple := operators are used on one line.
Until you learn and become familiar with a new syntax, there is
generally going to be a period you have to decipher it. I spent a long
time mentally translating list comprehensions into mathematical set
builder notation before it became second nature to me.
Even now, I know people who find decorators and comprehensions
indecipherable. Or at least, so they claim, and they aren't motivated to
bother learning them. Oh well, that's their loss.
Binding-expressions aren't like asynchronous programming, where the
entire coding paradigm is different, and you literally have to think
about your algorithms in another way. Whether you spell the binding
operation
target := expr
expr as target
expr -> target
target given target = expr
let target = expr
: target expr ;
(that last one is stolen from Forth, and should not be taken as a
serious suggestion)
is just a matter of spelling. We'll get used to whatever spelling it is.
Some may be more convenient than others, or more error-prone, or may be
harder to parse, but they're secondary issues. (Important, but still
secondary.) Fundamentally, the operation is the same regardless of the
spelling:
- evaluate an expression
- bind that value to a name
- return the value
(Except of course Nick's "given" suggestion is more complex, since the
returned value is not necessarily the same as the bound value.)
> * there are no difficult mental questions about evaluation order, e.g., in
> a bracketed expression having multiple assignments.
Aren't there just?
x = 1
print( x + x given x = 50 )
Will that print 100, or 51? Brackets ought to make it clearer:
(x + x given x = 50) # this ought to return 100
x + (x given x = 50) # this ought to return 51
but if I leave the brackets out, I have no idea what I'll get.
> Similarly, instead of
> (a.b(a) given a = c.d()) do I write (a.b(a := c.d())) or ((a :=
> c.d()).b(a)) ?
I would expect that your first example is a NameError:
a.b(a := c.d())
since Python evaluates arguments to a method *after* looking up the
method. So that corresponds to:
- look up "a" # NameError, unless you've already got an "a"
- look up "a.b"
- evaluate c.d()
- assign that value to a
- pass that to the a.b method we found earlier
What you probably want is the second version:
(a := c.d()).b(a)
which of course looks like utter crap. It might look better if we use at
least half-way sensible variable names and a more realistic looking
example, instead of obfuscated one-letter names.
(widget := widget_builder.new(*args)).method(widget)
> * it avoids the question of what happens when := is used in a switch: (a
> if (b := c) else d) Sometimes you want the assignment to happen
> unconditionally (a if (b:=c) else d) + b; sometimes you don't. How do you
> force one case or the other?
I think that this argument is really weak. The obvious answer is, if you
don't want the assignment to happen unconditionally, then don't do the
assignment unconditionally. Where you do it depends on when you want the
assignment to take place. There's no mystery here.
# unconditionally pop from a list
(spam if mylist.pop(idx) else eggs) + mylist
# conditionally pop from a list
(mylist.pop(idx) if condition else eggs) + mylist
(spam if condition else mylist.pop(idx)) + mylist
Take your choice of which you want:
(spam if (mylist := expr) else eggs) + mylist
((mylist := expr) if condition else eggs) + mylist
(spam if condition else (mylist := expr)) + mylist
Of course, in the last two cases, you're going to get a NameError when
you try to add mylist if the ternary operator took the wrong branch.
Unless you already defined mylist earlier.
If you want something else, you have to explain what you want.
> given makes it obvious by separating
> assignment from the usage of its assignment target.
This is just a purely mechanical source transformation from one to the
other. Just replace ":" with "mylist given mylist " and you are done.
# unconditional version
(spam if (mylist given mylist = expr) else eggs) + mylist
# conditional versions
((mylist given mylist = expr) if condition else eggs) + mylist
(spam if condition else (mylist given mylist = expr)) + mylist
If you can write the "given" versions, then just do the reverse
transformation and replace the redundant verbosity with a colon.
> Style:
> * it avoids the big style question of when to use and when not to use :=.
> (Even if you ask people not to, people are going to write the
> expression-statement a := b as a synonym for the statement a = b.)
What if they do? Is it really the end of the world if some ex-Pascal
coders or people with an over-developed desire for (foolish) consistency
add a colon to their statement level assignments?
Some people add semi-colons to their statements too. Do we care? No.
But if it aggitates people so much, then I'm perfectly happy with
Guido's suggestion that we simply ban top level binding expressions and
require them to leave the colons out.
> * it looks a lot like the existing Python "for" and "if" clauses, which
> also do in-expression assignments.
"if" clauses do an assignment? Have you borrowed the keys to Guido's
time machine, and are writing from 2025 and Python 4.2? *wink*
I don't think "given" expressions look even remotely similar to either.
for target in iterable:
if condition:
another_expr given target = expr
Aside from "all three use a keyword".
--
Steve
More information about the Python-ideas
mailing list