[Python-ideas] Coming up with an alternative to PEP 505's None-aware operators

David Foster davidfstr at gmail.com
Mon Feb 19 14:13:27 EST 2018


(1) This proposal serves well to eliminate repeated computations by 
allowing what is an inline assignment to a temporary variable. But it 
doesn't seem to make the case of None-aware operators any less verbose 
than they would be otherwise.

Proposal:
	value = ?it.strip()[4:].upper() if (?it=var1) is not None else None
Traditional:
	it = var1
	value = it.strip()[4:].upper() if it is not None else None

If we wanted to get rid of the "if it is not None else None" 
boilerplate, we'd need something more concise. For example - completely 
off the cuff - syntax like:
	value = var1?.strip()[4:].upper()

I'm not really interested in going down a rabbit hole of discussing 
those kinds of syntax alternatives on this thread. I just want to point 
out that the current proposal don't seem to make None-aware operations 
much more concise than they were before except in the case of complex 
subexpressions being None-tested, which I find uncommon in my own programs.

(2) In considering the proposal in the alternative light of specifically 
trying to eliminate complex subexpressions, I'll also put a +1 in for 
(expr as it) rather than (?it=) since I find it reads nicer. Also is 
consistent with existing syntax (with expr as var).

Cheers,
David

--
David Foster | Seattle, WA, USA

On 2/15/18 6:06 PM, Nick Coghlan wrote:
> The recent thread on variable assignment in comprehensions has
> prompted me to finally share
> https://gist.github.com/ncoghlan/a1b0482fc1ee3c3a11fc7ae64833a315 with
> a wider audience (see the comments there for some notes on iterations
> I've already been through on the idea).
>
> == The general idea ==
>
> The general idea would be to introduce a *single* statement local
> reference using a new keyword with a symbolic prefix: "?it"
>
> * `(?it=expr)` is a new atomic expression for an "it reference
> binding" (whitespace would be permitted around "?it" and "=", but PEP
> 8 would recommend against it in general)
> * subsequent subexpressions (in execution order) can reference the
> bound subexpression using `?it` (an "it reference")
> * `?it` is reset between statements, including before entering the
> suite within a compound statement (if you want a persistent binding,
> use a named variable)
> * for conditional expressions, put the reference binding in the
> conditional, as that gets executed first
> * to avoid ambiguity, especially in function calls (where it could be
> confused with keyword argument syntax), the parentheses around
> reference bindings are always required
> * unlike regular variables, you can't close over statement local
> references (the nested scope will get an UnboundLocalError if you try
> it)
>
> The core inspiration here is English pronouns (hence the choice of
> keyword): we don't generally define arbitrary terms in the middle of
> sentences, but we *do* use pronouns to refer back to concepts
> introduced earlier in the sentence. And while it's not an especially
> common practice, pronouns are sometimes even used in a sentence
> *before* the concept they refer to ;)
>
> If we did pursue this, then PEPs 505, 532, and 535 would all be
> withdrawn or rejected (with the direction being to use an it-reference
> instead).
>
> == Examples ==
>
> `None`-aware attribute access:
>
>     value = ?it.strip()[4:].upper() if (?it=var1) is not None else None
>
> `None`-aware subscript access:
>
>     value = ?it[4:].upper() if (?it=var1) is not None else None
>
> `None`-coalescense:
>
>     value = ?it if (?it=var1) is not None else ?it if (?it=var2) is
> not None else var3
>
> `NaN`-coalescence:
>
>     value = ?it if not math.isnan((?it=var1)) else ?it if not
> math.isnan((?that=var2)) else var3
>
> Conditional function call:
>
>     value = ?it() if (?it=calculate) is not None else default
>
> Avoiding repeated evaluation of a comprehension filter condition:
>
>     filtered_values = [?it for x in keys if (?it=get_value(x)) is not None]
>
> Avoiding repeated evaluation for range and slice bounds:
>
>     range((?it=calculate_start()), ?it+10)
>     data[(?it=calculate_start()):?it+10]
>
> Avoiding repeated evaluation in chained comparisons:
>
>     value if (?it=lower_bound()) <= value < ?it+tolerance else 0
>
> Avoiding repeated evaluation in an f-string:
>
>     print(f"{?it=get_value()!r} is printed in pure ASCII as {?it!a}
> and in Unicode as {?it}"
>
> == Possible future extensions ==
>
> One possible future extension would be to pursue PEP 3150, treating
> the nested namespace as an it reference binding, giving:
>
>     sorted_data = sorted(data, key=?it.sort_key) given ?it=:
>         def sort_key(item):
>             return item.attr1, item.attr2
>
> (A potential bonus of that spelling is that it may be possible to make
> "given ?it=:" the syntactic keyword introducing the suite, allowing
> "given" itself to continue to be used as a variable name)
>
> Another possible extension would be to combine it references with `as`
> clauses on if statements and while loops:
>
>     if (?it=pattern.match(data)) is not None as matched:
>         ...
>
>     while (?it=pattern.match(data)) is not None as matched:
>         ...
>
> == Why not arbitrary embedded assignments? ==
>
> Primarily because embedded assignments are inherently hard to read,
> especially in long expressions. Restricting things to one pronoun, and
> then pursuing PEP 3150's given clause in order to expand to multiple
> statement local names should help nudge folks towards breaking things
> up into multiple statements rather than writing ever more complex
> one-liners.
>
> That said, the ?-prefix notation is deliberately designed such that it
> *could* be used with arbitrary identifiers rather then being limited
> to a single specific keyword, and the explicit lack of closure support
> means that there wouldn't be any complex nested scope issues
> associated with lambda expressions, generator expressions, or
> container comprehensions.
>
> With that approach, "?it" would just be an idiomatic default name like
> "self" or "cls" rather than being a true keyword. Given arbitrary
> identifier support, some of the earlier examples might instead be
> written as:
>
>     value = ?f() if (?f=calculate) is not None else default
>     range((?start=calculate_start()), ?start+10)
>     value if (?lower=lower_bound()) <= value < ?lower+tolerance else 0
>
> The main practical downside to this approach is that *all* the
> semantic weight ends up resting on the symbolic "?" prefix, which
> makes it very difficult to look up as a new Python user. With a
> keyword embedded in the construct, there's a higher chance that folks
> will be able to guess the right term to search for (i.e. "python it
> expression" or "python it keyword").
>
> Another downside of this more flexible option is that it likely
> *wouldn't* be amenable to the "if expr as name:" syntax extension, as
> there wouldn't be a single defined pronoun expression to bind the name
> to.
>
> However, the extension to PEP 3150 would allow the statement local
> namespace to be given an arbitrary name:
>
>     sorted_data = sorted(data, key=?ns.sort_key) given ?ns=:
>         def sort_key(item):
>             return item.attr1, item.attr2
>
> Cheers,
> Nick.
>


More information about the Python-ideas mailing list