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

Nick Coghlan ncoghlan at gmail.com
Sat May 5 12:00:35 EDT 2018

On 5 May 2018 at 13:36, Tim Peters <tim.peters at gmail.com> wrote:

> [Nick Coghlan <ncoghlan at gmail.com>]
> > ...
> > The essence of the given clause concept would be to modify *these
> specific
> > cases* (at least initially) to allow the condition expression to be
> followed
> > by an inline assignment, of the form "given TARGET = EXPR".
> I'm not clear on what "these specific cases" are, specifically ;-)
> Conditions in "if", "elif", and "while" statement expressions?

Exactly the 3 cases presented (if/elif/while conditions). The usage in
comprehensions would mirror the usage in if statements, and avoid allowing
name bindings in arbitrary locations due to the implicity nested scopes
used by comprehensions.

Conditional expressions would be initially omitted since including them
would allow arbitrary name bindings in arbitrary locations via the quirky
"x if x given x = expr else x" spelling, and because "else" isn't as
distinctive an ending token as "given ... :", "given ... )", "given ... ]",
or "given ... }".

> Restricted to one "given" clause, or can they chain?  In a listcomp,
> is it one "given" clause per "if", or after at most one "if"?  Or is
> an "if" even needed at all in a listcomp?  For example,
>     [(f(x)**2, f(x)**3) for x in xs]
> has no conditions, and
>     [(fx := f(x))**2, fx**3) for x in xs]
> is one reasonable use for binding expressions.
>     [(fx**2, fx**3) for x in xs given fx = f(x)]
> reads better, although it's initially surprising (to my eyes) to find
> fx defined "at the end".  But no more surprising than the current:
>     [(fx**2, fx**3) for x in xs for fx in [f(x)]]
> trick.
There were a couple key reasons I left the "for x in y" case out of the
initial proposal:

1. The "for x in y" header is already quite busy, especially when tuple
unpacking is used in the assignment target
2. Putting the "given" clause at the end would make it ambiguous as to
whether it's executed once when setting up the iterator, or on every
3. You can stick in an explicit "if True" if you don't need the given
variable in the filter condition

    [(fx**2, fx**3) for x in xs if True given fx = f(x)]

And then once you've had an entire release where the filter condition was
mandatory for the comprehension form, allowing the "if True" in "[(fx**2,
fx**3) for x in xs given fx = f(x)]" to be implicit would be less ambiguous.


> It''s certain sanest as
>     if x**2 + y**2 > 9 given x, y = func_returning_twople():
> "given" really shines there!

Yep, that's why I don't have the same immediate reaction of "It would need
to be limited to simple names as targets" reaction as I do for assignment
expressions. It might still be a good restriction to start out with, though
(especially if we wanted to allow multiple name bindings in a single given


The one-letter variable name obscures that it doesn't
> actually reduce _redundancy_, though.  That is, in the current
>     match = pattern.search(data)
>     if match:
> it's obviously less redundant typing as:
>     if match := pattern.search(data):
> In
>     if match given match = pattern.search(data):
> the annoying visual redundancy (& typing) persists.

Right, but that's specific to the case where the desired condition really
is just "bool(target)". That's certainly likely to be a *common* use case,
but if we decide that it's *that* particular flavour of redundancy that
really bothers us, then there's always the "if expr as name:" spelling
(similar to the way that Python had "a and b" and "a or b" logical control
flow operators long before it got "a if c else b").

One more, a lovely (to my eyes) binding expression simplification
> requiring two bindings in an `if` test, taken from real-life code I
> happened to write during the PEP discussion:
>     diff = x - x_base
>     if diff:
>         g = gcd(diff, n)
>         if g > 1:
>             return g
> collapsed to the crisp & clear:
>     if (diff := x - x_base) and (g := gcd(diff, n)) > 1:
>         return g
> If only one trailing "given" clause can be given per `if` test
> expression, presumably I couldn't do that without trickery.

I was actually thinking that if we did want to allow multiple assignments,
and we limited targets to single names, we could just use a comma as a

    if diff and g > 1 given diff = x - x_base, g = gcd(diff, n):
        return g

Similar to import statements, optional parentheses could be included in the
grammar, allowing the name bindings to be split across multiple lines:

    if diff and g > 1 given (
        diff = x - x_base,
        g = gcd(diff, n),
        return g

(Other potential separators would be ";", but that reads weirdly to me
since my brain expects the semi-colon to end the entire statement, and
"and", but that feels overly verbose, while also being overly different
from its regular meaning)

>   If it's
> more general,
>     if (diff given diff = x _ xbase) and g > 1 given g = gcd(diff, n):
> reads worse to my eyes (perhaps because of the "visual redundancy"
> thing again), while
>    if diff and g > 1 given diff = x - x_base given g = gcd(diff, n):
> has my eyes darting all over the place, and wondering which of the
> trailing `given` clauses executes first.

I find that last effect is lessened when using the comma as a separator
within the given clause rather than repeating the keyword itself.


Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20180506/2fedd290/attachment.html>

More information about the Python-ideas mailing list