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

Nick Coghlan ncoghlan at gmail.com
Sun Feb 18 20:57:39 EST 2018

On 17 February 2018 at 02:31, Ethan Furman <ethan at stoneleaf.us> wrote:
> On 02/15/2018 11:55 PM, Nick Coghlan wrote:
>> However, while I think that looks nicer in general, we'd still have to
>> choose between two surprising behaviours:
>> * implicitly delete the statement locals after the statement where
>> they're set (which still overwrites any values previously bound to
>> those names, similar to what happens with exception clauses)
> If we're overwriting locals anyway, don't delete it.  The good reason for
> unsetting an exception variable doesn't apply here.
>> * skip deleting, which means references to subexpressions may last
>> longer than expected (and we'd have the problem where embedded
>> assignments could overwrite existing local variables)
> Odds are good that we'll want/need that assignment even after the immediate
> expression it's used in.  Let it stick around.

If we want to use a subexpression in multiple statements, then regular
assignment statements already work fine - breaking out a separate
variable assignment only feels like an inconvenience when we use a
subexpression twice in a single statement, and then don't need it any

By contrast, if we have an implicit del immediately after the
statement for any statement local variables, then naming a
subexpression only extends its life to the end of the statement, not
to the end of the current function, and it's semantically explicit
that you *can't* use statement locals to name subexpressions that
*aren't* statement local.

The other concern I have with any form of statement local variables
that can overwrite regular locals is that we'd be reintroducing the
problem that comprehensions have in Python 2.x: unexpectedly rebinding
things in non-obvious ways. At least with an implicit "del" the error
would be more readily apparent, and if we disallow closing over
statement local variables (which would be reasonable, since closures
aren't statement local either), then we can avoid interfering with
regular locals without needing to introduce a new execution scope.

So let's consider a spec for statement local variable semantics that
looks like this:

1. Statement local variables do *not* appear in locals()
2. Statement local variables are *not* visible in nested scopes (of any kind)
3. Statement local variables in compound statement headers are visible
in the body of that compound statement
4. Due to 3, statement local variable references will be syntactically
distinct from regular local variable references
5. Nested uses of the same statement local variable name will shadow
outer uses, rather than overwriting them

The most discreet syntactic marker we have available is a single
leading dot, which would allow the following (note that in the simple
assignment cases, breaking out a preceding assignment would be easy,
but the perk of the statement local spelling is that it works in *any*
expression context):

    value = .s.strip()[4:].upper() if (var1 as .s) is not None else None

    value = .s[4:].upper() if (var1 as .s) is not None else None

    value = .v if (var1 as .v) is not None else .v if (var2 as .v)
is not None else var3

    value = .v if not math.isnan((var1 as .v)) else tmp if not
math.isnan((var2 as .v)) else var3

    value = .f() if (calculate as .f) is not None else default

    filtered_values = [.v for x in keys if (get_value(x) as .v) is not None]

    range((calculate_start() as .start), .start+10)
    data[(calculate_start() as .start):.start+10]

    value if (lower_bound() as .min_val) <= value < .min_val+tolerance else 0

    print(f"{(get_value() as .v)!r} is printed in pure ASCII as
{.v!a} and in Unicode as {.v}")

    if (pattern.search(data) as .m) is not None:
        # .m is available here as the match result
        # .m is also available here (but will always be None given the
    # .m is no longer available here

Except clauses would be updated to allow the "except ExceptionType as
.exc" spelling, which would give full statement local semantics (i.e.
disallow closing over the variable, hide it from locals), rather than
just deleting it at the end of the clause execution.

Similarly, with statements would allow "with cm as .enter_result" to
request statement local semantics for the enter result. (One potential
concern here would be the not-immediately-obvious semantic difference
between "with (cm as .the_cm):" and "with cm as .enter_result:").

To make that work at an implementation level we'd then need to track
the following in the compiler:

* the current nested statement level in the current compilation (so we
can generate distinct names at each level)
* a per-statement set of local variable names (so we can clear them at
the end of the statement)
* the largest number of concurrently set statement local variables (so
we can allocate space for them in the frame)
* the storage offset to use for each statement local variable

and then frames would need an additional storage area for statement
locals, as well as new opcodes for accessing them.

Adding yet more complexity to an already complicated scoping model is
an inherently dubious proposal, but at the same time, it does provide
a way to express "and" and "or" semantics in terms of statement local
variables and conditional expressions, and comparison chaining in
terms of statement local variables and the "and" operator (so
conceptually this kind of primitive does already exist in the
language, just only as an operator-specific special case inside the


Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia

More information about the Python-ideas mailing list