[Python-ideas] Alternative to PEP 532: delayed evaluation of expressions

Eric V. Smith eric at trueblade.com
Sun Nov 6 08:06:27 EST 2016


Creating a new thread, instead of hijacking the PEP 532 discussion.

 From PEP 532:

 > Abstract
 > ========
 >
 > Inspired by PEP 335, PEP 505, PEP 531, and the related discussions, 
this PEP
 > proposes the addition of a new protocol-driven circuit breaking 
operator to
 > Python that allows the left operand to decide whether or not the 
expression
 > should short circuit and return a result immediately, or else continue
 > on with evaluation of the right operand::
 >
 >     exists(foo) else bar
 >     missing(foo) else foo.bar()

Instead of new syntax that only works in this one specific case, I'd 
prefer a more general solution. I accept being "more general" probably 
seals the deal in killing any proposal!

I realize the following proposal has at least been hinted at before, but 
I couldn't find a specific discussion about it. Since it applies to the 
short-circuiting issues addressed by PEP 532 and its predecessors, I 
thought I'd bring it up here. It could also be used to solve some of the 
problems addressed by the rejected PEP 463 (Exception-catching 
expressions). See also PEP 312 (Simple Implicit Lambda). It might also 
be usable for some of the use cases presented in PEP 501 (General 
purpose string interpolation, aka i-strings).

I'd rather see the ability to have unevaluated expressions, that can 
later be evaluated. I'll use backticks here to mean: "parse, but do not 
execute the enclosed code". This produces an object that can later be 
evaluated with a new builtin I'll call "evaluate_now". Obviously these 
are strawmen, and partly chosen to be ugly and unacceptable names and 
symbols in the form I'll discuss here.

Then you could write a function:

eval_else(`foo.bar`, `some_func()`)

whose value is foo.bar, unless foo.bar cannot be evaluated, in which 
case the value is some_func().

def eval_else(expr, fallback, exlist=(AttributeError,)):
     try:
         return evaluate_now(expr)
     except exlist:
         return evaluate_now(fallback)

Exactly which exceptions you catch is up to you. Of course there's the 
chance that someone would pass in something for which the caught 
exception is too broad, and it's raised deep inside evaluating the first 
expression, but that's no different than catching exceptions now. Except 
I grant that hiding the try/except inside a called function increases 
the risk.

Like f-strings, the expressions are entirely created at the site they're 
specified inside ``. So they'd have access to locals and globals, etc., 
at the definition site.

def x(foo, i):
     return eval_else(`foo.bar`, `some_func(i, __name__)`)

And like the expressions in f-strings, they have to be valid 
expressions. But unlike f-strings, they aren't evaluated right when 
they're encountered. The fact that they may never be evaluated is one of 
their features.

For example the if/else expression:

if_else(`y`, x is None, `x.a`)

could be defined as being exactly like:

y if x is None else x.a

including only evaluating x.a if x is not None.

def if_else(a, test, b):
     if test:
         return evaluate_now(a)
     return evaluate_now(b)

You could do fancier things that require more than 2 expressions.

Whether `` returns an AST that could later be manipulated, or it's 
something else that's opaque is another discussion. Let's assume it's 
opaque for now.

You could go further and say that any argument to a function that's 
specially marked would get an unevaluated expression. Suppose that you 
can mark arguments as & to mean "takes an unevaluated expression". Then 
you could write:

def if_else(&a, test, &b):
     if test:
         return evaluate_now(a)
     return evaluate_now(b)

And call it as:
if_else(y, x is None, x.a)

But now you've made it non-obvious at the caller site exactly what's 
happening. There are other downsides, such as only being able to create 
an unevaluated expression when calling a function. Or maybe that's a 
good thing!

In any event, having unevaluated expressions would open up more 
possibilities than just the short-circuit evaluation model. And it 
doesn't involve a new protocol.

Eric.


More information about the Python-ideas mailing list