[Python-ideas] PEP 532: A circuit breaking operator and protocol

Nick Coghlan ncoghlan at gmail.com
Mon Nov 14 09:11:54 EST 2016


On 14 November 2016 at 19:01, Ryan Fox <ryan at rcfox.ca> wrote:
> (Hi, I'm a first-time poster. I was inspired by Raymond Hettinger's keynote
> at PyCon CA to look at new PEPs and comment on them. Hopefully, I'm not
> committing any faux-pas in my post!)
>
> 1) I don't think this proposal sufficiently handles falsy values.
>
> What is the expected value of x in the following?
>
>     x = exists(0) else 1
>
> Since the PEP specifically states that exists() is to check that the input
> is not None, as a user, I would expect x == 0. However, from my
> interpretation, it appears that the value 0 would still be directly
> evaluated for truthiness and we would get x == 1.

No, the conditional branching would be based on exists.__bool__ (or,
in the current working draft, is_not_none.__bool__), and that would be
"0 is not None", which would be True and hence short-circuit.

> I don't fully get what the purpose of __then__ and __else__ are meant to be,
> but it seems like instead of this:
>
>     type(_lhs).__then__(_lhs) if _lhs else type(_lhs).__else__(_lhs, RHS)
>
> you would want:
>
>     LHS if type(_lhs).__then__(_lhs) else RHS

`__then__` is responsible for *unwrapping* the original value from the
circuit breaker when it short-circuits: it's what allows the overall
expression to return "0", even though the truth check is done based on
"0 is not None".

> Where __then__ returns a simple True/False result. (Maybe the name __then__
> doesn't make sense in that case.)

We already have a method for that: __bool__.

However, it has exactly the problem you describe, which is why "0 or
expr" will always short-circuit, and why "(0 is not None) or expr"
will return "True".

> 2) My initial reaction was that `else` doesn't belong in an expression, but
> I guess there's already precedent for that. (I actually wasn't aware of the
> `x if y else z` expression until I read this PEP!)
>
> I'm already not a fan of the overloading of else in the cases of for/else
> and try/else. (Are there other uses? It's a hard thing to search on
> Google...) Now, we're going to have `else`s that aren't anchored to other
> statements. You've always known that an `else` belonged to the preceding
> if/for/try at the same level of indentation. (Or in the same expression.)
> Is there a chance of a missing colon silently changing the meaning of code?
>
>     if foo():
>         a = 1
>     else
>         bar()
>
> (Probably not...)

No, due to Python's line continuation rules - you'd also need
parentheses or a backslash to avoid getting a SyntaxError on the
unfinished line.

It does create amibiguities around conditional expressions though,
hence why that comes up as one of the main pragmatic concerns with the
idea.

> Are two missing spaces too outrageous?
>
>     x = yiffoo() else bar()
>
> I'm not 100% sure, but I think that a current parser would see that as a
> syntax error very early, as opposed to having to wait for it to try to find
> 'yiffoo' at run-time.

That style of error is already possible with the other keyword based operators:

    x = notfoo()
    y = xorfoo()
    z = yandfoo()

As you not, the main defense is that this will usually be a name
error, picked up either at runtime or by a static code analyser.

> 3) Towards the end, you propose some very Perl-like syntax:
>
>     print(some_expensive_query()) if verbosity > 2
>
> This seems completely unrelated to the rest of the PEP, and will likely
> invite people to propose an `unless` operator if implemented. (Followed by
> an `until` statement.)   :)
>
> While it does read more naturally like English, it seems superfluous in a
> programming language.

De Morgan's laws [1] mean that 'and' and 'or' are technically
redundant with each other, as given 'not', you can always express one
in terms of the other:

    X and Y --> not ((not X) or (not Y))
    X or Y --> not ((not X) and (not Y))

However, writing out the laws like that also makes it clear why
they're not redundant in practice: the inverted forms involve
double-negatives that make them incredibly hard to read.

Those rules impact this PEP by way of the fact that in "LHS if COND
else RHS", the "if" and "else" are actually in the same logical
relation to each other as "and" and "or" are in "COND and LHS or RHS".

Accordingly, if "LHS if COND else RHS" were to be reformulated as a
compound instruction built from two binary instructions (akin to the
way comparison chaining works) as considered in the "Risks and
Concerns" section about the language level inconsistencies that the
current draft introduces, then we'd expect De Morgan's laws to hold
there as well:

    Y if X --> not ((not X) else (not Y))
    X else Y --> not ((not Y) if (not X))

It hadn't occurred to me to include that observation in the PEP while
updating it to switch to that base design, but it really should be
there as an additional invariant that well-behaved symmetric circuit
breakers should adhere to.

[1] https://en.wikipedia.org/wiki/De_Morgan%27s_laws

> 4) The proposal shows how it fixes some common pain points:
>
>    value = missing(obj) else obj.field.of.interest
>    value = missing(obj) else obj["field"]["of"]["interest"]
>
> But it doesn't address very similar ones:
>
>     missing(obj) else missing(obj.field) else missing(obj.field.of) else
> obj.field.of.interest
>     obj.get('field', {}).get('of', {}).get('interest')
>
> (The first example shows how it would be handled with the PEP in its current
> state.)
>
> Maybe these are too far out of scope, I'm not sure. They feel very similar
> to me though.

The current draft already indicates it doesn't aim to compete with the
"?." or "?[]" proposals in PEP 505 (which handle these two cases), and
the next draft drops the competition with "??" as well. That way, the
proposed circuit breakers for the PEP 505 cases can just be
"operator.is_none" and "operator.is_not_none".

> I hope these are useful comments and not too nit-picky.

They were very helpful, and picked up a key technical point that I'd
missed in the revised proposal I'm currently working on. I think I
also need to restore a diagram that Mark E. Haase drew for an earlier
draft of the PEP that may make it easier for folks to visualise the
related control flow.

Cheers,
Nick.

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


More information about the Python-ideas mailing list