[Python-ideas] Inline assignments using "given" clauses
tim.peters at gmail.com
Fri May 4 23:36:03 EDT 2018
[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?
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)]]
> While the leading keyword would allow TARGET to be an arbitrary assignment
> target without much chance for confusion, it could also be restricted to
> simple names instead (as has been done for PEP 572.
The problem with complex targets in general assignment expressions is
that, despite trying, I found no plausible use case for (at least)
unpacking syntax. As in, e.g.,
x, y = func_returning_twople()
if x**2 + y**2 > 9: # i.e., distance > 3, but save expensive sqrt
The names can be _unpacked_ in a general assignment expression, but
there appears to be no sane way then to _use_ the names in the test.
This may be as good as it gets:
if [(x, y := func_returning_twople()). x**2 + y**2 > 9][-1]:
That reminds me of the hideous
(condition and [T] or [F])
idiom I "invented" long ago to get the effect (in all cases) of the current
T if condition else F
That was intended to be goofy fun at the time, but I was appalled to
see people later use it ;-)
It''s certain sanest as
if x**2 + y**2 > 9 given x, y = func_returning_twople():
"given" really shines there!
> With that spelling, the three examples above would become:
> # Exactly one branch is executed here
> if m given m = pattern.search(data):
> elif m given m = other_pattern.search(data)):
Which is OK. The one-letter variable name obscures that it doesn't
actually reduce _redundancy_, though. That is, in the current
match = pattern.search(data)
it's obviously less redundant typing as:
if match := pattern.search(data):
if match given match = pattern.search(data):
the annoying visual redundancy (& typing) persists.
> # This name is rebound on each trip around the loop
> while m given m = pattern.search(remaining_data):
Also fine, but also doesn't reduce redundancy.
> # "f(x)" is only evaluated once on each iteration
> result = [(x, y, x/y) for x in data if y given y = f(x)]
As above, the potential usefulness of "given" in a listcomp doesn't
really depend on having a conditional. Or on having a listcomp
either, for that matter ;-)
r2, r3 = fx**2, fx**3 given fx = f(x)
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
g = gcd(diff, n)
if g > 1:
collapsed to the crisp & clear:
if (diff := x - x_base) and (g := gcd(diff, n)) > 1:
If only one trailing "given" clause can be given per `if` test
expression, presumably I couldn't do that without trickery. If it's
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.
More information about the Python-ideas