[Python-ideas] Inline assignments using "given" clauses

Ed Kellett e+python-ideas at kellett.im
Sat May 12 17:33:48 EDT 2018


On 2018-05-12 18:24, Steven D'Aprano wrote:
> On Sat, May 12, 2018 at 08:16:07AM -0700, Neil Girdhar wrote:
>> I love given compared with := mainly because
>> 
>> Simpler is better than complex: * given breaks a complex statement
>>  into two simpler ones,
> 
> [snip]
> 
> Nick's syntax is *much more complicated* than the := syntax. Any 
> arbitrary expression can appear on the left of "given". It need not 
> even involve the binding target! So to make a fair comparison, I 
> ought to compare:
> 
> target := expr
> 
> which evaluates a single expression, binds it, and returns it, to:
> 
> another_expr given target := expr
> 
> which evaluates "expr", binds it to "target", evaluates a SECOND 
> unrelated expression, and returns that.
> 
> If you want to argue that this is more useful, then fine, say so.
> But to say that it is *simpler* makes no sense to me.
> 
> Option 1: evaluate and bind a single expression
> 
> Option 2: exactly the same as Option 1, and then evaluate a second 
> expression
> 
> How do you justify that Option 2 "given", which does everything := 
> does PLUS MORE, is simpler than Option 1?

Your interpretation of the argument you quote, as I understand it, is
that "`given` is simpler than `:=`". I interpreted it as, and would
argue myself, more like "`given` makes *the code that uses it* simpler".

The reason the `given` syntax might be simpler has nothing to do with
the number of syntactic elements (brainfuck is down the hall...).
Rather, it is that `given` separates things:

    if m.group(2) in words given m = word_re.match(s):

versus:

    if (m := word_re.match(s)).group(2) in words:

In the `:=` version, the assignment is embedded in the expression. It's
different. Most of the time it will save at least a few characters. But
it's not obviously--and certainly not objectively--simpler.

One somewhat concrete difference I can think of is that in expressions
that refer to the result multiple times, the `:=` assignment must always
be positioned such that it is the first to be evaluated, moving around
should that ever change. In cases where evaluation depends on runtime
information (if-else, and, or), I'm not actually sure what you would do.

In general, I feel like you're focusing on the simplicity or otherwise
of the operator itself, rather than whether it has a simplifying effect
on code that uses it. As you say in part 2 (which arrived while I was
still taking forever to write this; sorry if it reads a bit confused),
we'll learn the syntax, eventually. What's not so explicitly spelled out
is that we'll be reading new code that uses it forever.

> average = 0 smooth_signal = [(average := (1-decay)*average +
> decay*x) for x in signal] assert average == smooth_signal[-1]

Like, holy smoke, man. Sure, `:=` is probably better for cramming
side-effects into list comprehensions. Please don't cram side-effects
into list comprehensions.

> total = 0 running_totals = [total := total + x for x in xs]
> 
> versus
> 
> total = 0 running_totals = [total given total = total + x for x in xs]

What is it with the cramming side-effects into list comprehensions‽ Do
you realise itertools.accumulate exists?

See the OP:

On 2018-05-04 13:06, Nick Coghlan wrote:
> 3. Sharing values between filtering clauses and result expressions in
> comprehensions:
> 
> result = [(x, y, x/y) for x in data if (y := f(x))]

I don't have a more concrete example of this to hand, mostly because I
can't do this today, and faking it is hard enough (I didn't even know
about the `for value in [stuff]` hack until this thread) that I just
write for loops.

On 2018-05-12 18:24, Steven D'Aprano wrote:
> By your argument, augmented assignment is more complex, and we ought 
> to prefer splitting it into two separate operations x = x + 1 because
> that's simpler.
> 
> I think I agree that x = x + 1 *is* simpler. We can understand it by 
> understanding the two parts separately: x+1, followed by assignment.
> 
> Whereas += requires us to understand that the syntax not only calls
> a dunder method __iadd__ (or __add__ if that doesn't exist), which 
> potentially can operate in place, but it also does an assignment,
> all in one conceptual operation. There's a whole lot of extra
> complexity there.

While I'm not sorry that I can do `x += 1`, it does have a substantial
cost--one that is perhaps justified by the fact that it's *not*
equivalent to `x = x + 1`, and that the possibilities it opens up are
both useful and easy to understand. It's not simpler in and of itself,
but that's not really the issue.

On 2018-05-12 19:27, Steven D'Aprano wrote:
> 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)

I believe that that's missing the point: to wit, in

    x.method(y)

is `x` or `y` evaluated first? I didn't know. I don't think the order is
documented anywhere or guaranteed not to change. I don't know what other
languages do in general, though I know in C it's explicitly unspecified.

So, sure, you can do this fine with `:=`. But it forces your code to
depend on what I'd regard as a subtlety of the implementation.

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 484 bytes
Desc: OpenPGP digital signature
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20180512/b30a06f9/attachment.sig>


More information about the Python-ideas mailing list